Hardware components | ||||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
Software apps and online services | ||||||
| ||||||
Hand tools and fabrication machines | ||||||
| ||||||
| ||||||
| ||||||
| ||||||
|
Fire Fighting Safety Issues
https://www.nifc.gov/safety/safety_documents/Fatalities-by-Year.pdf
The problem and safety concern is the Burnover problem. as you can see by the attached link and PDF is Burnover is a major cause of death and injury for fire fighters. Burnover is when a fire changes direction and goes over the heads of fire Fighters. This causes Burns, Asphyxiation and death.
the Following article in the firehouse.com website explains what happens when a fire changes directions https://www.firehouse.com/lodds/news/10460154/sd-firefighter-killed-4-others-hurt-in-burnover
My solution utilizes a Particle LTE Microcontroller/ Wind Direction and speed Sensors / Air Quality Sensor / Smoke Sensor /Temperature and Humidity Sensor as well as a thermal camera . I am also developing a Satellite version for commercial deployment for areas without LTE coverage.
The Theory of Operation
* Drone will be deploy able by the fire fighters on the ground when they enter a fire zone area.
*Drone will be deployed to a high spot whether it is on a hill side or onto a tree top or a buildings roof.
* Once deployed instant analytics are uploaded to the cloud where the fire fighters can be alerted to temperature and humidity changes wind direction and speed as well as having a camera overview of the situation.
* Real time alerts and information of the on ground conditions will allow for quick decision making and safety escape routes .
*More than 1 drone can be deployed in a given area giving triangulation data for a better overview of speed and direction of the fire.
Benefits
Low cost as compared to helicopter deployment or fire-watch.
Disposable if lost in a fire or destroyed by accident
Different variations available for longer term deployment and fire prevention and warning.
Contest Variation: Quick deploy system with separate sensor and solar power sensor system with LTE Cloud connectivity High Speed Camera to watch for hot spots for close Suburban Proximity to assets and People.
Variation 1. 100% Solar Powered and Satellite Connected deploy able by GPS location. Long term sensor solution separate from Flying circuit for Forest and Mountain deployment where LTE is not available.
Variation 2. 100% Solar Powered LTE Connected Sensor system separate from Flying circuit. Deploy able to Suburban areas with LTE Connectivity for early warning of potential fire conditions(Low Humidity / High Temperature /High wind scenarios)
The Drone and Parts used in the Build
Whether you're an agriculturalist, a professional meteorologist or a weather hobbyist, building your own weather station can be a really rewarding project. When you're measuring weather, however, you need some pretty specialized sensors. This kit represents the three core components of weather measurement: wind speed, wind direction and rainfall.
None of the sensors in this kit contain active electronics, instead they use sealed magnetic reed switches and magnets so you'll need to source a voltage to take any measurements. The positive side of this is that the sensors are easy to read.
Sparkfun Sensors
Sparkfun VOC/O2 Sensor
Sparkfun Smoke Sensor
Sparkfun Temperature Humidity Sensor
Lightning Detector
Particle Electron LTE
https://store.particle.io/collections/cellular/products/electron-3g-americas
The Build
I mocked up the first initial build not tightening up anything to get an idea as to the complexity and difficulty of the build as well as making note as to where everything goes and to see if I need to make any changes to my design And to make note of any other tools I may need .
After I took the wold drone apart and Used nail polish on all screws and bolts ..
Image 1: I used Double tape on the Pads for the Voltage controllers for the fan motors as it provided a bit of room and padding. I also unscrewed and used Nail polish in the silver screws in the picture before torquing them back down..
Image 2: I used some extra double sided tape( but left the red covering on ) on the sides of the voltage controllers to prevent wearing on the controllers from wiring or the Motor Support tubes.
Image 3: Image shows the Nail Polish on the bottom screws of the motor Mounts.
Image 4: This Image is of the Drone Leg Mounts with the Nail Polish on the Back side and all they way through.
Image 6: I tye wrapped the Motor power feed down and used some double backed tape on the back of the controller to keep it in one spot for now.
PCB Build
I Had my Boards manufactured and Built by QUINT Tech Hong Kong LTD.
contact Freya at freya@quint-tech.com for a great Quote at Fantastic Pricing for a complete build.
This PCB can be mounted directly above the NXP Controller using 1" standoffs.
It has plug-ins for the above sensors as well as a separate control board for using where there is no LTE communications but there is LoRa. The separate detachable board has a SAMD21G18 series Chip and is Arduino Programmable using the MAKRWAN1300 Board Drivers and Program .
BOM
Attached
Programming
The programming of these boards is in 2 parts. 1 is the Particle programming witch will send sensor analytics to the cloud for alarms and location information. As well as temperature , humidity , VOC , CO , Smoke Intensity and Lightning distances and intensity..
Particle Programming
Adding the NXP temperature Sensor
NXP Drone Programming
First Follow the Instruction in the Following Link
https://nxp.gitbook.io/hovergames/downloads#rddrone-fmuk66-px4-bootloader
Once bootloader is installed you should see this in your Devices
Monitoring and Logging the Information
The Dashboard where the Fire conditions can be monitored
Adding the NXP IR temperature Sensor
I added the IR sensor to the front of the Drone and routed the wiring through one of the tubes.
// This #include statement was automatically added by the Particle IDE.
#include <Adafruit_MLX90614.h>
#include <SF_CCS811.h>
#include <Adafruit_BME280.h>
#include "MAX30105.h"
#include <SPI.h>
#include <Wire.h>
#include "SparkFun_AS3935.h"
#define AS3935_ADDR 0x03
#define INDOOR 0x12
#define OUTDOOR 0xE
#define LIGHTNING_INT 0x08
#define DISTURBER_INT 0x04
#define NOISE_INT 0x01
//======================================
#define BME_SCK D4
#define BME_MISO D3
#define BME_MOSI D2
#define BME_CS D5
#define SEALEVELPRESSURE_HPA (1013.25)
//======================================
uint32_t lastReset = 0;
#define CCS811_ADDR 0x5B //Default I2C Address
//#define CCS811_ADDR 0x5A //Alternate I2C Address
CCS811 mySensor(CCS811_ADDR);
//======================================
Adafruit_MLX90614 mlx = Adafruit_MLX90614();
//======================================
Adafruit_BME280 bme; // I2C
unsigned long delayTime;
unsigned long duration;
//=======================================
SparkFun_AS3935 lightning(AS3935_ADDR);
MAX30105 particleSensor;
const int lightningInt = 4;
int intVal = 0;
int noise = 5; // Value between 1-7
int disturber = 10; // Value between 1-10
//======================================
long startTime;
long samplesTaken = 0; //Counter for calculating the Hz or read rate
//=================**WeatherVane**=====================
int button = A4; // button is connected to D0
int LED = D7; // LED is connected to D1
int val = 0;
//========================================
unsigned long timeSinceLastTick = 0;
unsigned long lastTick = 0;
String windDir = "";
float windSpeed = 0;
long secsClock = 0;
#define WIND_SPD_PIN A4
#define WIND_DIR_PIN A5
#define AIR_RST 4
#define AIR_WAKE 15
#define DONE_LED 7
//============================================
void setup() {
Serial.begin(9600);
Wire.begin();
lastReset = millis();
//======================================
pinMode(LED, OUTPUT); // sets pin as output
pinMode(button, INPUT_PULLDOWN); // sets pin as input
pinMode(A4, INPUT_PULLDOWN);
pinMode(WIND_SPD_PIN, INPUT_PULLDOWN); // Wind speed sensor
attachInterrupt(digitalPinToInterrupt(WIND_SPD_PIN), windTick, FALLING);
//=========================================
Serial.println("MAX30105 Basic Readings Example");
particleSensor.begin();
particleSensor.setup(); //Configure sensor. Use 6.4mA for LED drive
byte ledBrightness = 255; //Options: 0=Off to 255=50mA
byte sampleAverage = 2; //Options: 1, 2, 4, 8, 16, 32
byte ledMode = 3; //Options: 1 = Red only, 2 = Red + IR, 3 = Red + IR + Green
int sampleRate = 50; //Options: 50, 100, 200, 400, 800, 1000, 1600, 3200
int pulseWidth = 118; //Options: 69, 118, 215, 411
int adcRange = 4096; //Options: 2048, 4096, 8192, 16384
particleSensor.setup(ledBrightness, sampleAverage, ledMode, sampleRate, pulseWidth, adcRange); //Configure sensor with these settings
particleSensor.enableDIETEMPRDY();
startTime = millis();
//=======================================
bme.begin();
mySensor.begin();
Adafruit_MLX90614 mlx = Adafruit_MLX90614();
//========================================
pinMode(lightningInt, INPUT);
Serial.println("AS3935 Franklin Lightning Detector");
if( !lightning.begin() ) { // Initialize the sensor.
Serial.println ("Lightning Detector did not start up, freezing!");
while(1);
}
else
Serial.println("Schmow-ZoW, Lightning Detector Ready!");
lightning.setIndoorOutdoor(OUTDOOR);
//=======================================
}
void loop() {
char message1[120];
char message2[120];
char message3[120];
char message4[128];
char message5[128];
if (millis() - lastReset > 5*60000UL) {
// System.reset();
}
//======================================
static unsigned long outLoopTimer = 0;
static unsigned long clockTimer = 0;
static unsigned long tempMSClock = 0;
tempMSClock += millis() - clockTimer;
clockTimer = millis();
while (tempMSClock >= 1000)
{
secsClock++;
tempMSClock -= 1000;
}
Serial.print("Wind dir**: ");
windDirCalc(analogRead(WIND_DIR_PIN));
Serial.print(" ");
Serial.print(windDir);
Serial.println(" Degrees ");
//==================================================================
int WindVolts;
analogRead(WIND_DIR_PIN);
WindVolts=(WIND_DIR_PIN);
Serial.print("WindVolts ");
Serial.println(WindVolts);
if (WindVolts < 1.50) windDir="202.5"; {
if (WindVolts < 1.4) windDir = "180";
if (WindVolts < 2.93) windDir = "247.5";
if (WindVolts < 3.08) windDir = "225";
if (WindVolts < 3.0) windDir = "292.5";
if (WindVolts < 3.1) windDir = "270";
if (WindVolts < 0.32) windDir = "112.5";
if (WindVolts < 0.90) windDir = "135";
if (WindVolts < 3.2) windDir = "337.5";
if (WindVolts < 3.3) windDir = "315";
if (WindVolts < 0.41) windDir = "67.5";
if (WindVolts < 0.45) windDir = "90";
if (WindVolts < 1.98) windDir = "22.5";
if (WindVolts < 2.25) windDir = "45";
if (WindVolts < 3.25) windDir = "0";
Serial.print("WindDIR ");
Serial.println(windDir);
Particle.publish("windDir",windDir, PRIVATE);
delay(10000);
}
//==================================================================
if (timeSinceLastTick != 0) windSpeed = 1000.0/timeSinceLastTick;
Serial.print("Windspeed: ");
duration = pulseIn(A4, LOW);
timeSinceLastTick = 0;
Serial.print(windSpeed*1.94);
Serial.print(" mph ");
Serial.print(windSpeed*2.4);
Serial.println(" kph ");
delay(10);
//=====================================
val = pinReadFast(button); // read the input pin
digitalWriteFast(LED, val);
delay(1);
duration = pulseIn(A4, HIGH);
// Serial.printlnf("%d us", duration);
//==================**BME280**====================
Serial.print("Temperature = ");
Serial.print(bme.readTemperature());
Serial.print(" *C ");
Serial.print("Pressure = ");
Serial.print(bme.readPressure() / 100.0F);
Serial.print(" hPa ");
Serial.print("Approx. Altitude = ");
Serial.print(bme.readAltitude(SEALEVELPRESSURE_HPA));
Serial.print(" m ");
Serial.print("Humidity = ");
Serial.print(bme.readHumidity());
Serial.print(" % ");
Serial.println();
//========================**MAX30105***===============================
samplesTaken++;
Serial.print(" R[");
Serial.print(particleSensor.getRed());
float RED = particleSensor.getRed();
Serial.print("] IR[");
float IR = particleSensor.getIR();
Serial.print(particleSensor.getIR());
Serial.print("] G[");
float GRN = particleSensor.getGreen();
Serial.print(particleSensor.getGreen());
Serial.print("] Hz[");
Serial.print((float)samplesTaken / ((millis() - startTime) / 1000.0), 2);
Serial.print("]");
float temperature = particleSensor.readTemperature();
Serial.print("temperatureC=");
Serial.print(temperature, 4);
Serial.println();
particleSensor.nextSample(); //We're finished with this sample so move to next sample
//=======================**Lightning***================================
if(digitalRead(lightningInt) == HIGH){
byte distance = lightning.distanceToStorm();
Serial.print("Approximately: ");
Serial.print(distance);
Serial.println("km away!");
sprintf(message3,"KM %s",distance);
// Particle.publish("String3", message3, PRIVATE);
intVal = lightning.readInterruptReg();
if(intVal == NOISE_INT){
Serial.println("Noise ");
sprintf(message3,"Noise %s",distance);
// Particle.publish("String3", message3, PRIVATE);
}
else if(intVal == DISTURBER_INT){
byte distance = lightning.distanceToStorm();
Serial.print("Approximately: ");
Serial.print(distance);
Serial.println("km away!");
sprintf(message3,"KM %s",distance);
Particle.publish("String3", message3, PRIVATE);
Serial.println("Disturber.");
sprintf(message3,"Disturber %d",distance);
// Particle.publish("String3", message3, PRIVATE);
}
else if(intVal == LIGHTNING_INT){
Serial.println("Lightning Strike Detected!");
byte distance = lightning.distanceToStorm();
Serial.print("Approximately: ");
Serial.print(distance);
Serial.println("km away!");
sprintf(message3,"KM %d",distance);
// Particle.publish("String3", message3, PRIVATE);
}
}
//===============================**CCS811**==============================
mySensor.readAlgorithmResults();
Serial.print("CO2[");
Serial.print(mySensor.getCO2());
Serial.print("] tVOC[");
Serial.print(mySensor.getTVOC());
Serial.print("]");
Serial.println();
//========================================================================
Serial.print("Ambient = "); Serial.print(mlx.readAmbientTempC());
Serial.print("*C\tObject = "); Serial.print(mlx.readObjectTempC()); Serial.println("*C");
Serial.print("Ambient = "); Serial.print(mlx.readAmbientTempF());
Serial.print("*F\tObject = "); Serial.print(mlx.readObjectTempF()); Serial.println("*F");
Serial.println();
sprintf(message2,"CO2%d,TVOC%d,WNDSP%.2f,RED%.2f,IR%.2f,GRN%.2f,IR_TMP%f,Ambient%.2f,tObject%.2f",mySensor.getCO2(),mySensor.getTVOC(),(windSpeed*2.4),RED,IR,GRN,temperature,mlx.readAmbientTempC(),mlx.readObjectTempC());
sprintf(message1,"BME_TMP%.1f,BME_PRESS%f,BME_ALT%.2f,BME_HUM%.2f",bme.readTemperature(),(bme.readPressure() / 100.0F),(bme.readAltitude(SEALEVELPRESSURE_HPA)),bme.readHumidity());
sprintf(message4,"%d,%d,%.2f,%.2f,%.2f,%.2f,%.2f,%.1f,%f,%.1f,%.2f,%.2f,%.2f",mySensor.getCO2(),mySensor.getTVOC(),(windSpeed*2.4),RED,IR,GRN,temperature,(bme.readTemperature()),(bme.readPressure()/100.0F),(bme.readAltitude(SEALEVELPRESSURE_HPA),(bme.readHumidity(),mlx.readAmbientTempC(),mlx.readObjectTempC())));
sprintf(message5,"%d,%d,%.2f,%.2f,%.2f,%.2f,%f,%.1f,%f,%.2f,%.2f",mySensor.getCO2(),mySensor.getTVOC(),(windSpeed*2.4),RED,IR,GRN,temperature,bme.readTemperature(),(bme.readPressure() / 100.0F),(bme.readAltitude(SEALEVELPRESSURE_HPA)),bme.readHumidity());
Particle.publish("String5", message5 , PRIVATE);
Particle.publish("String2", message2 , PRIVATE);
Particle.publish("String1", message1 , PRIVATE);
Particle.publish("String4", message4 , PRIVATE);
//========================================================================
delay(10000); // Slow it down.
}
//========================================================================
//========================================================================
//========================================================================
//========================================================================
//========================================================================
void windTick(void) {
timeSinceLastTick = millis() - lastTick;
lastTick = millis();
}
void windDirCalc(int vin) {
if (vin < 1.50) windDir="202.5";
else if (vin < 1.4) windDir = "180";
else if (vin < 2.93) windDir = "247.5";
else if (vin < 3.08) windDir = "225";
else if (vin < 3.0) windDir = "292.5";
else if (vin < 3.1) windDir = "270";
else if (vin < 0.32) windDir = "112.5";
else if (vin < 0.90) windDir = "135";
else if (vin < 3.2) windDir = "337.5";
else if (vin < 3.3) windDir = "315";
else if (vin < 0.41) windDir = "67.5";
else if (vin < 0.45) windDir = "90";
else if (vin < 1.98) windDir = "22.5";
else if (vin < 2.25) windDir = "45";
else if (vin < 3.25) windDir = "0";
else windDir = "0";
}
Alerts
Temperature change / Humidity/ Wind Direction /Wind Speed alerts can be programmed in to send an SMS message, Email , Or sound an alarm
The Easiest way to do this is to program it into Node Red and send an SMS message when there is a change of wind Direction and / Or Speed
Go to Twilio https://www.twilio.com/ Sign up and configure your Account And configure the Twilio Node in Node red
Conclusion
There are a few things I am going to add to the drone when I have more money is a camera System for search and rescue and more sensor capability including a long range Flame sensor (In Production now)
Final Build Pictures
Final Build Cost
3D Print
I designed my Case with Fusion_360 its an excellent drawing program for making boxes etc for my projects
I wanted the Box to be Spray resistant but still let air through the vents for monitoring the Air quality and be sleek and aerodynamic a little bit as well.
Detailed Build Cost
Drone and Controller $356.00
Camera System $125.96
Battery $25.00-$45.00
Raspberry Pi4 2Gig $79.99
Air Speed Sensor $75.00
Wind Direction Sensor $66.90
Sub Total $728.85
PCB Build Cost Per Board
PCB $5.00
PCB Parts $65.86
Shipping $20.00
3D Box Build Cost
ProtoLabs $253.00
Other Parts
Particle Electron $65.00
Solar Panel $
Wiring,Nuts and Bolts $25.00
Misc Parts and Pieces
Resources (the following links were used in the design and production of this entry)not in any particular order of importance.
https://cportal.protolabs.com/myaccount/en-US/quote/modify.do?quoteId=772935&contactId=132134
https://i.ytimg.com/vi/bhvjfC5qenQ/maxresdefault.jpg
https://www.firehouse.com/lodds/news/10460154/sd-firefighter-killed-4-others-hurt-in-burnover
https://theintegralschoolblogdotorg.files.wordpress.com/2016/12/forest-fires-1.jpg
https://nxp.gitbook.io/hovergames/
/*
This is a library for the ASM AS3935 Franklin Lightning Detector
By: Elias Santistevan
SparkFun Electronics
Date: January, 2019
License: This code is public domain but you buy me a beer if you use this and we meet someday (Beerware license).
Feel like supporting our work? Buy a board from SparkFun!
*/
#include "SparkFun_AS3935.h"
// Default constructor, to be used with SPI
SparkFun_AS3935::SparkFun_AS3935() { }
// Another constructor with I2C but receives address from user.
SparkFun_AS3935::SparkFun_AS3935( i2cAddress address ) { _address = address; }
bool SparkFun_AS3935::begin( TwoWire &wirePort )
{
// Startup time requires 2ms for the LCO and 2ms more for the RC oscillators
// which occurs only after the LCO settles. See "Timing" under "Electrical
// Characteristics" in the datasheet.
delay(4);
_i2cPort = &wirePort;
// _i2cPort->begin(); A call to Wire.begin should occur in sketch
// to avoid multiple begins with other sketches.
// A return of 0 indicates success, else an error occurred.
_i2cPort->beginTransmission(_address);
uint8_t _ret = _i2cPort->endTransmission();
if(!_ret)
return true;
else
return false;
}
bool SparkFun_AS3935::beginSPI(uint8_t user_CSPin, uint32_t spiPortSpeed, SPIClass &spiPort)
{
// Startup time requires 2ms for the LCO and 2ms more for the RC oscillators
// which occurs only after the LCO settles. See "Timing" under "Electrical
// Characteristics" in the datasheet.
delay(4);
// I'll be using this as my indicator that SPI is to be used and not I2C.
_i2cPort = NULL;
_spiPort = &spiPort;
_spiPortSpeed = spiPortSpeed; // Make sure it's not 500kHz or it will cause feedback with antenna.
_cs = user_CSPin;
pinMode(_cs, OUTPUT);
digitalWrite(_cs, HIGH);// Deselect the Lightning Detector.
_spiPort->begin(); // Set up the SPI pins.
// Bit order is different for ESP32
#ifdef ESP32
SPI.setBitOrder(SPI_MSBFIRST);
#else
SPI.setBitOrder(MSBFIRST);
#endif
return true;
}
// REG0x00, bit[0], manufacturer default: 0.
// The product consumes 1-2uA while powered down. If the board is powered down
// the the TRCO will need to be recalibrated: REG0x08[5] = 1, wait 2 ms, REG0x08[5] = 0.
// SPI and I-squared-C remain active when the chip is powered down.
void SparkFun_AS3935::powerDown()
{
_writeRegister(AFE_GAIN, POWER_MASK, 1, 0);
}
// REG0x3A bit[7].
// This register holds the state of the timer RC oscillator (TRCO),
// after it has been calibrated. The TRCO in this case needs to be recalibrated
// after power down. The following function wakes the IC, sends the "Direct Command" to
// CALIB_RCO register REG0x3D, waits 2ms and then checks that it has been successfully
// calibrated. Note that I-squared-C and SPI are active during power down.
bool SparkFun_AS3935::wakeUp()
{
_writeRegister(AFE_GAIN, POWER_MASK, 0, 0); // Set the power down bit to zero to wake it up
if(calibrateOsc())
return true;
else
return false;
}
// REG0x00, bits [5:1], manufacturer default: 10010 (INDOOR).
// This function changes toggles the chip's settings for Indoors and Outdoors.
void SparkFun_AS3935::setIndoorOutdoor( uint8_t _setting )
{
if((_setting == INDOOR) || (_setting == OUTDOOR))
{ }
else
return;
if(_setting == INDOOR)
_writeRegister(AFE_GAIN, GAIN_MASK, INDOOR, 1);
if(_setting == OUTDOOR)
_writeRegister(AFE_GAIN, GAIN_MASK, OUTDOOR, 1);
}
// REG0x00, bits [5:1], manufacturer default: 10010 (INDOOR).
// This function returns the indoor/outdoor settting.
uint8_t SparkFun_AS3935::readIndoorOutdoor(){
uint8_t regVal = _readRegister(AFE_GAIN);
return ((regVal &= ~GAIN_MASK) >> 1);
}
// REG0x01, bits[3:0], manufacturer default: 0010 (2).
// This setting determines the threshold for events that trigger the
// IRQ Pin.
void SparkFun_AS3935::watchdogThreshold( uint8_t _sensitivity )
{
if( (_sensitivity < 1) || (_sensitivity > 10) )// 10 is the max sensitivity setting
return;
_writeRegister(THRESHOLD, THRESH_MASK, _sensitivity, 0);
}
// REG0x01, bits[3:0], manufacturer default: 0010 (2).
// This function returns the threshold for events that trigger the
// IRQ Pin.
uint8_t SparkFun_AS3935::readWatchdogThreshold(){
uint8_t regVal = _readRegister(THRESHOLD);
return (regVal &= (~THRESH_MASK));
}
// REG0x01, bits [6:4], manufacturer default: 010 (2).
// The noise floor level is compared to a known reference voltage. If this
// level is exceeded the chip will issue an interrupt to the IRQ pin,
// broadcasting that it can not operate properly due to noise (INT_NH).
// Check datasheet for specific noise level tolerances when setting this register.
void SparkFun_AS3935::setNoiseLevel( uint8_t _floor )
{
if( (_floor < 1) || (_floor > 7) )
return;
_writeRegister(THRESHOLD, NOISE_FLOOR_MASK, _floor, 4);
}
// REG0x01, bits [6:4], manufacturer default: 010 (2).
// This function will return the set noise level threshold: default is 2.
uint8_t SparkFun_AS3935::readNoiseLevel(){
uint8_t regVal = _readRegister(THRESHOLD);
return (regVal & ~NOISE_FLOOR_MASK) >> 4;
}
// REG0x02, bits [3:0], manufacturer default: 0010 (2).
// This setting, like the watchdog threshold, can help determine between false
// events and actual lightning. The shape of the spike is analyzed during the
// chip's signal validation routine. Increasing this value increases robustness
// at the cost of sensitivity to distant events.
void SparkFun_AS3935::spikeRejection( uint8_t _spSensitivity )
{
if( (_spSensitivity < 1) || (_spSensitivity > 11) )
return;
_writeRegister(LIGHTNING_REG, SPIKE_MASK, _spSensitivity, 0);
}
// REG0x02, bits [3:0], manufacturer default: 0010 (2).
// This function returns the value of the spike rejection register. This value
// helps to differentiate between events and acutal lightning, by analyzing the
// shape of the spike during chip's signal validation routine.
// Increasing this value increases robustness at the cost of sensitivity to distant events.
uint8_t SparkFun_AS3935::readSpikeRejection(){
uint8_t regVal = _readRegister(LIGHTNING_REG);
return (regVal &= ~SPIKE_MASK);
}
// REG0x02, bits [5:4], manufacturer default: 0 (single lightning strike).
// The number of lightning events before IRQ is set high. 15 minutes is The
// window of time before the number of detected lightning events is reset.
// The number of lightning strikes can be set to 1,5,9, or 16.
void SparkFun_AS3935::lightningThreshold( uint8_t _strikes )
{
uint8_t bits;
if( _strikes == 1)
bits = 0;
else if( _strikes == 5)
bits = 1;
else if( _strikes == 9)
bits = 2;
else if( _strikes == 16)
bits = 3;
else
return;
_writeRegister(LIGHTNING_REG, LIGHT_MASK, bits, 4);
}
// REG0x02, bits [5:4], manufacturer default: 0 (single lightning strike).
// This function will return the number of lightning strikes must strike within
// a 15 minute window before it triggers an event on the IRQ pin. Default is 1.
uint8_t SparkFun_AS3935::readLightningThreshold(){
uint8_t regVal = _readRegister(LIGHTNING_REG);
regVal &= ~LIGHT_MASK;
regVal >>= 4; // Front of the line.
if(regVal == 0)
return 1;
else if(regVal == 1)
return 5;
else if(regVal == 2)
return 9;
else if(regVal == 3)
return 16;
else
return regVal;
}
// REG0x02, bit [6], manufacturer default: 1.
// This register clears the number of lightning strikes that has been read in
// the last 15 minute block.
void SparkFun_AS3935::clearStatistics(bool _clearStat)
{
if(_clearStat != true)
return;
//Write high, then low, then high to clear.
_writeRegister(LIGHTNING_REG, STAT_MASK, 1, 6);
_writeRegister(LIGHTNING_REG, STAT_MASK, 0, 6);
_writeRegister(LIGHTNING_REG, STAT_MASK, 1, 6);
}
// REG0x03, bits [3:0], manufacturer default: 0.
// When there is an event that exceeds the watchdog threshold, the register is written
// with the type of event. This consists of two messages: INT_D (disturber detected) and
// INT_L (Lightning detected). A third interrupt INT_NH (noise level too HIGH)
// indicates that the noise level has been exceeded and will persist until the
// noise has ended. Events are active HIGH. There is a one second window of time to
// read the interrupt register after lightning is detected, and 1.5 after
// disturber.
uint8_t SparkFun_AS3935::readInterruptReg()
{
// A 2ms delay is added to allow for the memory register to be populated
// after the interrupt pin goes HIGH. See "Interrupt Management" in
// datasheet.
delay(2);
uint8_t _interValue;
_interValue = _readRegister(INT_MASK_ANT);
_interValue &= INT_MASK;
return(_interValue);
}
// REG0x03, bit [5], manufacturere default: 0.
// This setting will change whether or not disturbers trigger the IRQ Pin.
void SparkFun_AS3935::maskDisturber(bool _state)
{
if(_state == true || _state == false)
{ }
else
return;
_writeRegister(INT_MASK_ANT, DISTURB_MASK, _state, 5);
}
// REG0x03, bit [5], manufacturere default: 0.
// This setting will return whether or not disturbers trigger the IRQ Pin.
uint8_t SparkFun_AS3935::readMaskDisturber(){
uint8_t regVal = _readRegister(INT_MASK_ANT);
return (regVal &= ~DISTURB_MASK) >> 5;
}
// REG0x03, bit [7:6], manufacturer default: 0 (16 division ratio).
// The antenna is designed to resonate at 500kHz and so can be tuned with the
// following setting. The accuracy of the antenna must be within 3.5 percent of
// that value for proper signal validation and distance estimation.
void SparkFun_AS3935::changeDivRatio(uint8_t _divisionRatio)
{
uint8_t bits;
if(_divisionRatio == 16)
bits = 0;
else if(_divisionRatio == 32)
bits = 1;
else if(_divisionRatio == 64)
bits = 2;
else if(_divisionRatio == 128)
bits = 3;
else
return;
_writeRegister(INT_MASK_ANT, DIV_MASK, bits, 6);
}
// REG0x03, bit [7:6], manufacturer default: 0 (16 division ratio).
// This function returns the current division ratio of the resonance frequency.
// The antenna resonance frequency should be within 3.5 percent of 500kHz, and
// so when modifying the resonance frequency with the internal capacitors
// (tuneCap()) it's important to keep in mind that the displayed frequency on
// the IRQ pin is divided by this number.
uint8_t SparkFun_AS3935::readDivRatio(){
uint8_t regVal = _readRegister(INT_MASK_ANT);
regVal &= ~DIV_MASK;
regVal >>= 6; // Front of the line.
if( regVal == 0 )
return 16;
else if(regVal == 1)
return 32;
else if(regVal == 2)
return 64;
else if (regVal == 3)
return 128;
else
return UNKNOWN_ERROR;
}
// REG0x07, bit [5:0], manufacturer default: 0.
// This register holds the distance to the front of the storm and not the
// distance to a lightning strike.
uint8_t SparkFun_AS3935::distanceToStorm()
{
uint8_t _dist = _readRegister(DISTANCE);
_dist &= DISTANCE_MASK;
return(_dist);
}
// REG0x08, bits [5,6,7], manufacturer default: 0.
// This will send the frequency of the oscillators to the IRQ pin.
// _osc 1, bit[5] = TRCO - System RCO at 32.768kHz
// _osc 2, bit[6] = SRCO - Timer RCO Oscillators 1.1MHz
// _osc 3, bit[7] = LCO - Frequency of the Antenna
void SparkFun_AS3935::displayOscillator(bool _state, uint8_t _osc)
{
if( (_osc < 1) || (_osc > 3) )
return;
if(_state == true){
if(_osc == 1)
_writeRegister(FREQ_DISP_IRQ, OSC_MASK, 1, 5);
if(_osc == 2)
_writeRegister(FREQ_DISP_IRQ, OSC_MASK, 1, 6);
if(_osc == 3)
_writeRegister(FREQ_DISP_IRQ, OSC_MASK, 1, 7);
}
if(_state == false){
if(_osc == 1)
_writeRegister(FREQ_DISP_IRQ, OSC_MASK, 0, 5); //Demonstrative
if(_osc == 2)
_writeRegister(FREQ_DISP_IRQ, OSC_MASK, 0, 6);
if(_osc == 3)
_writeRegister(FREQ_DISP_IRQ, OSC_MASK, 0, 7);
}
}
// REG0x08, bits [3:0], manufacturer default: 0.
// This setting will add capacitance to the series RLC antenna on the product
// to help tune its resonance. The datasheet specifies being within 3.5 percent
// of 500kHz to get optimal lightning detection and distance sensing.
// It's possible to add up to 120pF in steps of 8pF to the antenna.
void SparkFun_AS3935::tuneCap(uint8_t farad)
{
if (farad < 0 || farad > 120)
return;
else if ( farad % 8 != 0)
return;
else
farad /= 8;
_writeRegister(FREQ_DISP_IRQ, CAP_MASK, farad, 0);
}
// REG0x08, bits [3:0], manufacturer default: 0.
// This setting will return the capacitance of the internal capacitors. It will
// return a value from one to 15 multiplied by the 8pF steps of the internal
// capacitance.
uint8_t SparkFun_AS3935::readTuneCap(){
uint8_t regVal = _readRegister(FREQ_DISP_IRQ);
return ((regVal &= ~CAP_MASK) * 8); //Multiplied by 8pF
}
// LSB = REG0x04, bits[7:0]
// MSB = REG0x05, bits[7:0]
// MMSB = REG0x06, bits[4:0]
// This returns a 20 bit value that is the 'energy' of the lightning strike.
// According to the datasheet this is only a pure value that doesn't have any
// physical meaning.
uint32_t SparkFun_AS3935::lightningEnergy()
{
uint32_t _pureLight = _readRegister(ENERGY_LIGHT_MMSB);
_pureLight &= ENERGY_MASK;
_pureLight <<= 16;
_pureLight |= _readRegister(ENERGY_LIGHT_MSB);
_pureLight <<= 8;
_pureLight |= _readRegister(ENERGY_LIGHT_LSB);
return _pureLight;
}
// REG0x3D, bits[7:0]
// This function calibrates both internal oscillators The oscillators are tuned
// based on the resonance frequency of the antenna and so it should be trimmed
// before the calibration is done.
bool SparkFun_AS3935::calibrateOsc(){
_writeRegister(CALIB_RCO, WIPE_ALL, DIRECT_COMMAND, 0); // Send command to calibrate the oscillators
Serial.println("Calibrating Oscillators");
displayOscillator(true, 2);
delay(2); // Give time for the internal oscillators to start up.
displayOscillator(false, 2);
// Check it they were calibrated successfully.
uint8_t regValSrco = _readRegister(CALIB_SRCO);
uint8_t regValTrco = _readRegister(CALIB_TRCO);
regValSrco &= CALIB_MASK;
regValSrco >>= 6;
regValTrco &= CALIB_MASK;
regValTrco >>= 6;
if(!regValSrco && !regValTrco) // Zero upon success
return true;
else
return false;
}
// REG0x3C, bits[7:0]
// This function resets all settings to their default values.
void SparkFun_AS3935::resetSettings(){
_writeRegister(RESET, WIPE_ALL, DIRECT_COMMAND, 0);
}
// This function handles all I2C write commands. It takes the register to write
// to, then will mask the part of the register that coincides with the
// given register, and then write the given bits to the register starting at
// the given start position.
void SparkFun_AS3935::_writeRegister(uint8_t _wReg, uint8_t _mask, uint8_t _bits, uint8_t _startPosition)
{
if(_i2cPort == NULL) {
_spiWrite = _readRegister(_wReg); // Get the current value of the register
_spiWrite &= _mask; // Mask the position we want to write to
_spiWrite |= (_bits << _startPosition); // Write the given bits to the variable
_spiPort->beginTransaction(SPISettings(_spiPortSpeed, MSBFIRST, SPI_MODE1));
digitalWrite(_cs, LOW); // Start communication
_spiPort->transfer(_wReg); // Start write command at given register
_spiPort->transfer(_spiWrite); // Write to register
digitalWrite(_cs, HIGH); // End communcation
_spiPort->endTransaction();
}
else {
_i2cWrite = _readRegister(_wReg); // Get the current value of the register
_i2cWrite &= _mask; // Mask the position we want to write to.
_i2cWrite |= (_bits << _startPosition); // Write the given bits to the variable
_i2cPort->beginTransmission(_address); // Start communication.
_i2cPort->write(_wReg); // at register....
_i2cPort->write(_i2cWrite); // Write register...
_i2cPort->endTransmission(); // End communcation.
}
}
// This function reads the given register.
uint8_t SparkFun_AS3935::_readRegister(uint8_t _reg)
{
if(_i2cPort == NULL) {
_spiPort->beginTransaction(SPISettings(_spiPortSpeed, MSBFIRST, SPI_MODE1));
digitalWrite(_cs, LOW); // Start communication.
_spiPort->transfer(_reg |= SPI_READ_M); // Register OR'ed with SPI read command.
_regValue = _spiPort->transfer(0); // Get data from register.
// According to datsheet, the chip select must be written HIGH, LOW, HIGH
// to correctly end the READ command.
digitalWrite(_cs, HIGH);
digitalWrite(_cs, LOW);
digitalWrite(_cs, HIGH);
_spiPort->endTransaction();
return(_regValue);
}
else {
_i2cPort->beginTransmission(_address);
_i2cPort->write(_reg); // Moves pointer to register.
_i2cPort->endTransmission(false); // 'False' here sends a restart message so that bus is not released
_i2cPort->requestFrom(_address, 1); // Read the register, only ever once.
_regValue = _i2cPort->read();
return(_regValue);
}
}
/******************************************************************************
SparkFunBME280.h
BME280 Particle Photon and Core Driver
Orginal by: Marshall Taylor @ SparkFun Electronics
Particle adaption by: Markus Haack (https://github.com/mhaack)
https://github.com/mhaack/SparkFun_BME280
Development environment specifics:
Particle IDE or Web IDE
This code is released under the [MIT License](http://opensource.org/licenses/MIT).
Please review the LICENSE.md file included with this example. If you have any questions
or concerns with licensing, please contact techsupport@sparkfun.com.
Distributed as-is; no warranty is given.
******************************************************************************/
// Test derived class for base class SparkFunIMU
#ifndef __BME280_H__
#define __BME280_H__
#include "application.h"
#define I2C_MODE 0
#define SPI_MODE 1
//Register names:
#define BME280_DIG_T1_LSB_REG 0x88
#define BME280_DIG_T1_MSB_REG 0x89
#define BME280_DIG_T2_LSB_REG 0x8A
#define BME280_DIG_T2_MSB_REG 0x8B
#define BME280_DIG_T3_LSB_REG 0x8C
#define BME280_DIG_T3_MSB_REG 0x8D
#define BME280_DIG_P1_LSB_REG 0x8E
#define BME280_DIG_P1_MSB_REG 0x8F
#define BME280_DIG_P2_LSB_REG 0x90
#define BME280_DIG_P2_MSB_REG 0x91
#define BME280_DIG_P3_LSB_REG 0x92
#define BME280_DIG_P3_MSB_REG 0x93
#define BME280_DIG_P4_LSB_REG 0x94
#define BME280_DIG_P4_MSB_REG 0x95
#define BME280_DIG_P5_LSB_REG 0x96
#define BME280_DIG_P5_MSB_REG 0x97
#define BME280_DIG_P6_LSB_REG 0x98
#define BME280_DIG_P6_MSB_REG 0x99
#define BME280_DIG_P7_LSB_REG 0x9A
#define BME280_DIG_P7_MSB_REG 0x9B
#define BME280_DIG_P8_LSB_REG 0x9C
#define BME280_DIG_P8_MSB_REG 0x9D
#define BME280_DIG_P9_LSB_REG 0x9E
#define BME280_DIG_P9_MSB_REG 0x9F
#define BME280_DIG_H1_REG 0xA1
#define BME280_CHIP_ID_REG 0xD0 //Chip ID
#define BME280_RST_REG 0xE0 //Softreset Reg
#define BME280_DIG_H2_LSB_REG 0xE1
#define BME280_DIG_H2_MSB_REG 0xE2
#define BME280_DIG_H3_REG 0xE3
#define BME280_DIG_H4_MSB_REG 0xE4
#define BME280_DIG_H4_LSB_REG 0xE5
#define BME280_DIG_H5_MSB_REG 0xE6
#define BME280_DIG_H6_REG 0xE7
#define BME280_CTRL_HUMIDITY_REG 0xF2 //Ctrl Humidity Reg
#define BME280_STAT_REG 0xF3 //Status Reg
#define BME280_CTRL_MEAS_REG 0xF4 //Ctrl Measure Reg
#define BME280_CONFIG_REG 0xF5 //Configuration Reg
#define BME280_PRESSURE_MSB_REG 0xF7 //Pressure MSB
#define BME280_PRESSURE_LSB_REG 0xF8 //Pressure LSB
#define BME280_PRESSURE_XLSB_REG 0xF9 //Pressure XLSB
#define BME280_TEMPERATURE_MSB_REG 0xFA //Temperature MSB
#define BME280_TEMPERATURE_LSB_REG 0xFB //Temperature LSB
#define BME280_TEMPERATURE_XLSB_REG 0xFC //Temperature XLSB
#define BME280_HUMIDITY_MSB_REG 0xFD //Humidity MSB
#define BME280_HUMIDITY_LSB_REG 0xFE //Humidity LSB
//Class SensorSettings. This object is used to hold settings data. The application
//uses this classes' data directly. The settings are adopted and sent to the sensor
//at special times, such as .begin. Some are used for doing math.
//
//This is a kind of bloated way to do this. The trade-off is that the user doesn't
//need to deal with #defines or enums with bizarre names.
//
//A power user would strip out SensorSettings entirely, and send specific read and
//write command directly to the IC. (ST #defines below)
//
struct SensorSettings
{
public:
//Main Interface and mode settings
uint8_t commInterface;
uint8_t I2CAddress;
uint8_t chipSelectPin;
uint8_t runMode;
uint8_t tStandby;
uint8_t filter;
uint8_t tempOverSample;
uint8_t pressOverSample;
uint8_t humidOverSample;
};
//Used to hold the calibration constants. These are used
//by the driver as measurements are being taking
struct SensorCalibration
{
public:
uint16_t dig_T1;
int16_t dig_T2;
int16_t dig_T3;
uint16_t dig_P1;
int16_t dig_P2;
int16_t dig_P3;
int16_t dig_P4;
int16_t dig_P5;
int16_t dig_P6;
int16_t dig_P7;
int16_t dig_P8;
int16_t dig_P9;
uint8_t dig_H1;
int16_t dig_H2;
uint8_t dig_H3;
int16_t dig_H4;
int16_t dig_H5;
uint8_t dig_H6;
};
//This is the man operational class of the driver.
class BME280
{
public:
//settings
SensorSettings settings;
SensorCalibration calibration;
int32_t t_fine;
//Constructor generates default SensorSettings.
//(over-ride after construction if desired)
BME280( void );
//~BME280() = default;
//Call to apply SensorSettings.
//This also gets the SensorCalibration constants
uint8_t begin( void );
//Software reset routine
void reset( void );
//Returns the values as floats.
float readFloatPressure( void );
float readFloatAltitudeMeters( void );
float readFloatAltitudeFeet( void );
float readFloatHumidity( void );
//Temperature related methods
float readTempC( void );
float readTempF( void );
//The following utilities read and write
//ReadRegisterRegion takes a uint8 array address as input and reads
//a chunk of memory into that array.
void readRegisterRegion(uint8_t*, uint8_t, uint8_t );
//readRegister reads one register
uint8_t readRegister(uint8_t);
//Reads two regs, LSByte then MSByte order, and concatenates them
//Used for two-byte reads
int16_t readRegisterInt16( uint8_t offset );
//Writes a byte;
void writeRegister(uint8_t, uint8_t);
};
#endif // End of __BME280_H__ definition check
#include <SF_CCS811.h>
#include <Adafruit_BME280.h>
#include "MAX30105.h"
#include <SPI.h>
#include <Wire.h>
#include "SparkFun_AS3935.h"
#define AS3935_ADDR 0x03
#define INDOOR 0x12
#define OUTDOOR 0xE
#define LIGHTNING_INT 0x08
#define DISTURBER_INT 0x04
#define NOISE_INT 0x01
//======================================
#define BME_SCK D4
#define BME_MISO D3
#define BME_MOSI D2
#define BME_CS D5
#define SEALEVELPRESSURE_HPA (1013.25)
//======================================
uint32_t lastReset = 0;
#define CCS811_ADDR 0x5B //Default I2C Address
//#define CCS811_ADDR 0x5A //Alternate I2C Address
CCS811 mySensor(CCS811_ADDR);
//======================================
Adafruit_BME280 bme; // I2C
unsigned long delayTime;
unsigned long duration;
//=======================================
SparkFun_AS3935 lightning(AS3935_ADDR);
MAX30105 particleSensor;
const int lightningInt = 4;
int intVal = 0;
int noise = 5; // Value between 1-7
int disturber = 10; // Value between 1-10
//======================================
long startTime;
long samplesTaken = 0; //Counter for calculating the Hz or read rate
//=================**WeatherVane**=====================
int button = A4; // button is connected to D0
int LED = D7; // LED is connected to D1
int val = 0;
//========================================
unsigned long timeSinceLastTick = 0;
unsigned long lastTick = 0;
String windDir = "";
float windSpeed = 0;
long secsClock = 0;
#define WIND_SPD_PIN A4
#define WIND_DIR_PIN A5
#define AIR_RST 4
#define AIR_WAKE 15
#define DONE_LED 7
//============================================
void setup() {
Serial.begin(9600);
WiFi.connect(WIFI_CONNECT_SKIP_LISTEN);
Wire.begin();
lastReset = millis();
//======================================
pinMode(LED, OUTPUT); // sets pin as output
pinMode(button, INPUT_PULLDOWN); // sets pin as input
pinMode(A4, INPUT_PULLDOWN);
pinMode(WIND_SPD_PIN, INPUT_PULLDOWN); // Wind speed sensor
attachInterrupt(digitalPinToInterrupt(WIND_SPD_PIN), windTick, FALLING);
//=========================================
Serial.println("MAX30105 Basic Readings Example");
particleSensor.begin();
particleSensor.setup(); //Configure sensor. Use 6.4mA for LED drive
byte ledBrightness = 255; //Options: 0=Off to 255=50mA
byte sampleAverage = 2; //Options: 1, 2, 4, 8, 16, 32
byte ledMode = 3; //Options: 1 = Red only, 2 = Red + IR, 3 = Red + IR + Green
int sampleRate = 50; //Options: 50, 100, 200, 400, 800, 1000, 1600, 3200
int pulseWidth = 118; //Options: 69, 118, 215, 411
int adcRange = 4096; //Options: 2048, 4096, 8192, 16384
particleSensor.setup(ledBrightness, sampleAverage, ledMode, sampleRate, pulseWidth, adcRange); //Configure sensor with these settings
particleSensor.enableDIETEMPRDY();
startTime = millis();
//=======================================
bme.begin();
mySensor.begin();
//========================================
pinMode(lightningInt, INPUT);
Serial.println("AS3935 Franklin Lightning Detector");
if( !lightning.begin() ) { // Initialize the sensor.
Serial.println ("Lightning Detector did not start up, freezing!");
while(1);
}
else
Serial.println("Schmow-ZoW, Lightning Detector Ready!");
lightning.setIndoorOutdoor(OUTDOOR);
//=======================================
}
void loop() {
char message1[120];
char message2[120];
char message3[120];
if (millis() - lastReset > 5*60000UL) {
System.reset();
}
//======================================
static unsigned long outLoopTimer = 0;
static unsigned long clockTimer = 0;
static unsigned long tempMSClock = 0;
tempMSClock += millis() - clockTimer;
clockTimer = millis();
while (tempMSClock >= 1000)
{
secsClock++;
tempMSClock -= 1000;
}
Serial.print("Wind dir: ");
windDirCalc(analogRead(WIND_DIR_PIN));
Serial.print(" ");
Serial.print(windDir);
Serial.println(" Degrees ");
if (timeSinceLastTick != 0) windSpeed = 1000.0/timeSinceLastTick;
Serial.print("Windspeed: ");
duration = pulseIn(A4, LOW);
timeSinceLastTick = 0;
Serial.print(windSpeed*1.94);
Serial.print(" mph ");
Serial.print(windSpeed*2.4);
Serial.println(" kph ");
delay(10);
//=====================================
val = pinReadFast(button); // read the input pin
digitalWriteFast(LED, val);
delay(1);
duration = pulseIn(A4, HIGH);
// Serial.printlnf("%d us", duration);
//==================**BME280**====================
Serial.print("Temperature = ");
Serial.print(bme.readTemperature());
Serial.print(" *C ");
Serial.print("Pressure = ");
Serial.print(bme.readPressure() / 100.0F);
Serial.print(" hPa ");
Serial.print("Approx. Altitude = ");
Serial.print(bme.readAltitude(SEALEVELPRESSURE_HPA));
Serial.print(" m ");
Serial.print("Humidity = ");
Serial.print(bme.readHumidity());
Serial.print(" % ");
Serial.println();
//========================**MAX30105***===============================
samplesTaken++;
Serial.print(" R[");
Serial.print(particleSensor.getRed());
float RED = particleSensor.getRed();
Serial.print("] IR[");
float IR = particleSensor.getIR();
Serial.print(particleSensor.getIR());
Serial.print("] G[");
float GRN = particleSensor.getGreen();
Serial.print(particleSensor.getGreen());
Serial.print("] Hz[");
Serial.print((float)samplesTaken / ((millis() - startTime) / 1000.0), 2);
Serial.print("]");
float temperature = particleSensor.readTemperature();
Serial.print("temperatureC=");
Serial.print(temperature, 4);
Serial.println();
particleSensor.nextSample(); //We're finished with this sample so move to next sample
//=======================**Lightning***================================
if(digitalRead(lightningInt) == HIGH){
intVal = lightning.readInterruptReg();
if(intVal == NOISE_INT){
Serial.println("Noise.");
}
else if(intVal == DISTURBER_INT){
Serial.println("Disturber.");
}
else if(intVal == LIGHTNING_INT){
Serial.println("Lightning Strike Detected!");
byte distance = lightning.distanceToStorm();
Serial.print("Approximately: ");
Serial.print(distance);
Serial.println("km away!");
}
}
//===============================**CCS811**==============================
mySensor.readAlgorithmResults();
Serial.print("CO2[");
Serial.print(mySensor.getCO2());
Serial.print("] tVOC[");
Serial.print(mySensor.getTVOC());
Serial.print("]");
Serial.println();
//========================================================================
sprintf(message3,"CO2 %d,TVOC %d,WNDSP %.2f,WNDDIR %.2f,RED %.2f,IR %.2f,GRN %.2f,IR_TMP %f",mySensor.getCO2(),mySensor.getTVOC(),(windSpeed*2.4),windDir,RED,IR,GRN,temperature);
sprintf(message1,"BME_TMP %.1f,BME_PRESS %f,BME_ALT %.2f,BME_HUM %.2f",bme.readTemperature(),(bme.readPressure() / 100.0F),(bme.readAltitude(SEALEVELPRESSURE_HPA)),bme.readHumidity());
Particle.publish("String2", message1, PRIVATE);
Particle.publish("String1", message3 , PRIVATE);
//========================================================================
delay(1000); // Slow it down.
}
//========================================================================
//========================================================================
//========================================================================
//========================================================================
//========================================================================
void windTick(void) {
timeSinceLastTick = millis() - lastTick;
lastTick = millis();
}
void windDirCalc(int vin) {
if (vin < 1.50) windDir="202.5";
else if (vin < 1.4) windDir = "180";
else if (vin < 2.93) windDir = "247.5";
else if (vin < 3.08) windDir = "225";
else if (vin < 3.0) windDir = "292.5";
else if (vin < 3.1) windDir = "270";
else if (vin < 0.32) windDir = "112.5";
else if (vin < 0.90) windDir = "135";
else if (vin < 3.2) windDir = "337.5";
else if (vin < 3.3) windDir = "315";
else if (vin < 0.41) windDir = "67.5";
else if (vin < 0.45) windDir = "90";
else if (vin < 1.98) windDir = "22.5";
else if (vin < 2.25) windDir = "45";
else if (vin < 3.25) windDir = "0";
else windDir = "0";
}
Main Test Program with all the Sensors
C/C++https://go.particle.io/shared_apps/5d8bc8c2fb89bf000593c1ba (Shareable Code)
#include <SPI.h>
#include <Wire.h>
#include "SparkFun_AS3935.h"
#include "SparkFun_AS3935.h"
#include "SparkFunBME280.h"
#include <Adafruit_CCS811.h>
#include "MAX30105.h"
#if defined(PARTICLE)
SYSTEM_THREAD(ENABLED)
#endif
const long interval = 1000;
unsigned long previousMillis = 0;
//==========================
MAX30105 particleSensor;
#define debug Serial
//==========================
Adafruit_CCS811 ccs;
//===========================
BME280 mySensor;
//=======================
#include <SPI.h>
#include <Wire.h>
#include "SparkFun_AS3935.h"
// 0x03 is default, but the address can also be 0x02, or 0x01.
#define AS3935_ADDR 0x03
#define INDOOR 0x12
#define OUTDOOR 0xE
#define LIGHTNING_INT 0x08
#define DISTURBER_INT 0x04
#define NOISE_INT 0x01
SparkFun_AS3935 lightning(AS3935_ADDR);
const int lightningInt = 4;
byte noiseFloor = 2;
byte watchDogVal = 2;
byte spike = 2;
byte lightningThresh = 1;
byte intVal = 0;
//========================
//****Weather Station***********
//========================
#define ENABLE_LIGHTNING
//SoftwareSerial imp(8, 9); // RX, TX into Imp pin 7
//Hardware pin definitions
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
// digital I/O pins
const byte WSPEED = 3;
const byte RAIN = 2;
const byte STAT1 = 7;
#ifdef ENABLE_LIGHTNING
const byte LIGHTNING_IRQ = 4; //Not really an interrupt pin, we will catch it in software
const byte slaveSelectPin = 10; //SS for AS3935
#endif
// analog I/O pins
const byte WDIR = A0;
const byte LIGHT = A1;
const byte BATT = A2;
const byte REFERENCE_3V3 = A3;
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
#ifdef ENABLE_LIGHTNING
//#include "AS3935.h" //Lighting dtector
#include <SPI.h> //Needed for lighting sensor
byte SPItransfer(byte sendByte);
AS3935 AS3935(SPItransfer, slaveSelectPin, LIGHTNING_IRQ);
#endif
long lastSecond; //The millis counter to see when a second rolls by
unsigned int minutesSinceLastReset; //Used to reset variables after 24 hours. Imp should tell us when it's midnight, this is backup.
byte seconds; //When it hits 60, increase the current minute
byte seconds_2m; //Keeps track of the "wind speed/dir avg" over last 2 minutes array of data
byte minutes; //Keeps track of where we are in various arrays of data
byte minutes_10m; //Keeps track of where we are in wind gust/dir over last 10 minutes array of data
long lastWindCheck = 0;
volatile long lastWindIRQ = 0;
volatile byte windClicks = 0;
#ifdef ENABLE_LIGHTNING
byte lightning_distance = 0;
#endif
byte windspdavg[120]; //120 bytes to keep track of 2 minute average
#define WIND_DIR_AVG_SIZE 120
int winddiravg[WIND_DIR_AVG_SIZE]; //120 ints to keep track of 2 minute average
float windgust_10m[10]; //10 floats to keep track of largest gust in the last 10 minutes
int windgustdirection_10m[10]; //10 ints to keep track of 10 minute max
volatile float rainHour[60]; //60 floating numbers to keep track of 60 minutes of rain
//These are all the weather values that wunderground expects:
int winddir; // [0-360 instantaneous wind direction]
float windspeedmph; // [mph instantaneous wind speed]
float windgustmph; // [mph current wind gust, using software specific time period]
int windgustdir; // [0-360 using software specific time period]
float windspdmph_avg2m; // [mph 2 minute average wind speed mph]
int winddir_avg2m; // [0-360 2 minute average wind direction]
float windgustmph_10m; // [mph past 10 minutes wind gust mph ]
int windgustdir_10m; // [0-360 past 10 minutes wind gust direction]
float humidity; // [%]
float tempf; // [temperature F]
float rainin; // [rain inches over the past hour)] -- the accumulated rainfall in the past 60 min
volatile float dailyrainin; // [rain inches so far today in local time]
//float baromin = 30.03;// [barom in] - It's hard to calculate baromin locally, do this in the agent
float pressure;
//float dewptf; // [dewpoint F] - It's hard to calculate dewpoint locally, do this in the agent
//These are not wunderground values, they are just for us
float batt_lvl = 11.8;
float light_lvl = 0.72;
// volatiles are subject to modification by IRQs
volatile unsigned long raintime, rainlast, raininterval, rain;
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
//Interrupt routines (these are called by the hardware interrupts, not by the main code)
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
void rainIRQ()
// Count rain gauge bucket tips as they occur
// Activated by the magnet and reed switch in the rain gauge, attached to input D2
{
raintime = millis(); // grab current time
raininterval = raintime - rainlast; // calculate interval between this and last event
if (raininterval > 10) // ignore switch-bounce glitches less than 10mS after initial edge
{
dailyrainin += 0.011; //Each dump is 0.011" of water
rainHour[minutes] += 0.011; //Increase this minute's amount of rain
rainlast = raintime; // set up for next event
}
}
void wspeedIRQ()
// Activated by the magnet in the anemometer (2 ticks per rotation), attached to input D3
{
if (millis() - lastWindIRQ > 10) // Ignore switch-bounce glitches less than 10ms (142MPH max reading) after the reed switch closes
{
lastWindIRQ = millis(); //Grab the current time
windClicks++; //There is 1.492MPH for each click per second.
}
}
//========================
void setup() {
Serial.begin(9600);
//=========================
Serial.println("CCS811 test");
(ccs.begin());
float temp = ccs.calculateTemperature();
ccs.setTempOffset(temp - 25.0);
//=========================
debug.begin(9600);
debug.println("MAX30105 Basic Readings Example");
// Initialize sensor
if (particleSensor.begin() == false) {
debug.println("MAX30105 was not found. Please check wiring/power. ");
while (1);
}
particleSensor.setup(); //Configure sensor. Use 6.4mA for LED drive
//=========================
mySensor.settings.commInterface = I2C_MODE;
mySensor.settings.I2CAddress = 0x77;
// 0, Sleep mode
// 1 or 2, Forced mode
// 3, Normal mode
mySensor.settings.runMode = 3; //Normal mode
// 0, 0.5ms
// 1, 62.5ms
// 2, 125ms
// 3, 250ms
// 4, 500ms
// 5, 1000ms
// 6, 10ms
// 7, 20ms
mySensor.settings.tStandby = 0;
// 0, filter off
// 1, coefficients = 2
// 2, coefficients = 4
// 3, coefficients = 8
// 4, coefficients = 16
mySensor.settings.filter = 4;
//tempOverSample can be:
// 0, skipped
// 1 through 5, oversampling *1, *2, *4, *8, *16 respectively
mySensor.settings.tempOverSample = 5;
//pressOverSample can be:
// 0, skipped
// 1 through 5, oversampling *1, *2, *4, *8, *16 respectively
mySensor.settings.pressOverSample = 5;
//humidOverSample can be:
// 0, skipped
// 1 through 5, oversampling *1, *2, *4, *8, *16 respectively
mySensor.settings.humidOverSample = 5;
Serial.begin(57600);
Serial.print("Program Started\n");
Serial.print("Starting BME280... result of .begin(): 0x");
//Calling .begin() causes the settings to be loaded
delay(10); //Make sure sensor had enough time to turn on. BME280 requires 2ms to start up.
Serial.println(mySensor.begin(), HEX);
Serial.print("Displaying ID, reset and ctrl regs\n");
Serial.print("ID(0xD0): 0x");
Serial.println(mySensor.readRegister(BME280_CHIP_ID_REG), HEX);
Serial.print("Reset register(0xE0): 0x");
Serial.println(mySensor.readRegister(BME280_RST_REG), HEX);
Serial.print("ctrl_meas(0xF4): 0x");
Serial.println(mySensor.readRegister(BME280_CTRL_MEAS_REG), HEX);
Serial.print("ctrl_hum(0xF2): 0x");
Serial.println(mySensor.readRegister(BME280_CTRL_HUMIDITY_REG), HEX);
Serial.print("\n\n");
Serial.print("Displaying all regs\n");
uint8_t memCounter = 0x80;
uint8_t tempReadData;
for(int rowi = 8; rowi < 16; rowi++ )
{
Serial.print("0x");
Serial.print(rowi, HEX);
Serial.print("0:");
for(int coli = 0; coli < 16; coli++ )
{
tempReadData = mySensor.readRegister(memCounter);
Serial.print((tempReadData >> 4) & 0x0F, HEX);//Print first hex nibble
Serial.print(tempReadData & 0x0F, HEX);//Print second hex nibble
Serial.print(" ");
memCounter++;
}
Serial.print("\n");
}
///*
Serial.print("\n\n");
Serial.print("Displaying concatenated calibration words\n");
Serial.print("dig_T1, uint16: ");
Serial.println(mySensor.calibration.dig_T1);
Serial.print("dig_T2, int16: ");
Serial.println(mySensor.calibration.dig_T2);
Serial.print("dig_T3, int16: ");
Serial.println(mySensor.calibration.dig_T3);
Serial.print("dig_P1, uint16: ");
Serial.println(mySensor.calibration.dig_P1);
Serial.print("dig_P2, int16: ");
Serial.println(mySensor.calibration.dig_P2);
Serial.print("dig_P3, int16: ");
Serial.println(mySensor.calibration.dig_P3);
Serial.print("dig_P4, int16: ");
Serial.println(mySensor.calibration.dig_P4);
Serial.print("dig_P5, int16: ");
Serial.println(mySensor.calibration.dig_P5);
Serial.print("dig_P6, int16: ");
Serial.println(mySensor.calibration.dig_P6);
Serial.print("dig_P7, int16: ");
Serial.println(mySensor.calibration.dig_P7);
Serial.print("dig_P8, int16: ");
Serial.println(mySensor.calibration.dig_P8);
Serial.print("dig_P9, int16: ");
Serial.println(mySensor.calibration.dig_P9);
Serial.print("dig_H1, uint8: ");
Serial.println(mySensor.calibration.dig_H1);
Serial.print("dig_H2, int16: ");
Serial.println(mySensor.calibration.dig_H2);
Serial.print("dig_H3, uint8: ");
Serial.println(mySensor.calibration.dig_H3);
Serial.print("dig_H4, int16: ");
Serial.println(mySensor.calibration.dig_H4);
Serial.print("dig_H5, int16: ");
Serial.println(mySensor.calibration.dig_H5);
Serial.print("dig_H6, uint8: ");
Serial.println(mySensor.calibration.dig_H6);
Serial.println();
//*/
//==========================
// When lightning is detected the interrupt pin goes HIGH.
pinMode(lightningInt, INPUT);
Serial.begin(115200);
Serial.println("AS3935 Franklin Lightning Detector");
Wire.begin(); // Begin Wire before lightning sensor.
if( !lightning.begin() ){ // Initialize the sensor.
Serial.println ("Lightning Detector did not start up, freezing!");
while(1);
}
else
Serial.println("Schmow-ZoW, Lightning Detector Ready!\n");
// "Disturbers" are events that are false lightning events. If you find
// yourself seeing a lot of disturbers you can have the chip not report those
// events on the interrupt lines.
lightning.maskDisturber(true);
int maskVal = lightning.readMaskDisturber();
Serial.print("Are disturbers being masked: ");
if (maskVal == 1)
Serial.println("YES");
else if (maskVal == 0)
Serial.println("NO");
// The lightning detector defaults to an indoor setting (less
// gain/sensitivity), if you plan on using this outdoors
// uncomment the following line:
//lightning.setIndoorOutdoor(OUTDOOR);
int enviVal = lightning.readIndoorOutdoor();
Serial.print("Are we set for indoor or outdoor: ");
if( enviVal == INDOOR )
Serial.println("Indoor.");
else if( enviVal == OUTDOOR )
Serial.println("Outdoor.");
else
Serial.println(enviVal, BIN);
// Noise floor setting from 1-7, one being the lowest. Default setting is
// two. If you need to check the setting, the corresponding function for
// reading the function follows.
lightning.setNoiseLevel(noiseFloor);
int noiseVal = lightning.readNoiseLevel();
Serial.print("Noise Level is set at: ");
Serial.println(noiseVal);
// Watchdog threshold setting can be from 1-10, one being the lowest. Default setting is
// two. If you need to check the setting, the corresponding function for
// reading the function follows.
lightning.watchdogThreshold(watchDogVal);
int watchVal = lightning.readWatchdogThreshold();
Serial.print("Watchdog Threshold is set to: ");
Serial.println(watchVal);
// Spike Rejection setting from 1-11, one being the lowest. Default setting is
// two. If you need to check the setting, the corresponding function for
// reading the function follows.
// The shape of the spike is analyzed during the chip's
// validation routine. You can round this spike at the cost of sensitivity to
// distant events.
lightning.spikeRejection(spike);
int spikeVal = lightning.readSpikeRejection();
Serial.print("Spike Rejection is set to: ");
Serial.println(spikeVal);
// This setting will change when the lightning detector issues an interrupt.
// For example you will only get an interrupt after five lightning strikes
// instead of one. Default is one, and it takes settings of 1, 5, 9 and 16.
// Followed by its corresponding read function. Default is zero.
lightning.lightningThreshold(lightningThresh);
uint8_t lightVal = lightning.readLightningThreshold();
Serial.print("The number of strikes before interrupt is triggerd: ");
Serial.println(lightVal);
// When the distance to the storm is estimated, it takes into account other
// lightning that was sensed in the past 15 minutes. If you want to reset
// time, then you can call this function.
//lightning.clearStatistics();
// The power down function has a BIG "gotcha". When you wake up the board
// after power down, the internal oscillators will be recalibrated. They are
// recalibrated according to the resonance frequency of the antenna - which
// should be around 500kHz. It's highly recommended that you calibrate your
// antenna before using these two functions, or you run the risk of schewing
// the timing of the chip.
//lightning.powerDown();
//delay(1000);
//if( lightning.wakeUp() )
// Serial.println("Successfully woken up!");
//else
//Serial.println("Error recalibrating internal osciallator on wake up.");
// Set too many features? Reset them all with the following function.
lightning.resetSettings();
//==========================
// wdt_reset(); //Pet the dog
// wdt_disable(); //We don't want the watchdog during ini
pinMode(WSPEED, INPUT_PULLUP); // input from wind meters windspeed sensor
pinMode(RAIN, INPUT_PULLUP); // input from wind meters rain gauge sensor
pinMode(WDIR, INPUT);
pinMode(LIGHT, INPUT);
pinMode(BATT, INPUT);
pinMode(REFERENCE_3V3, INPUT);
pinMode(STAT1, OUTPUT);
midnightReset(); //Reset rain totals
#ifdef ENABLE_LIGHTNING
startLightning(); //Init the lighting sensor
#endif
seconds = 0;
lastSecond = millis();
// attach external interrupt pins to IRQ functions
attachInterrupt(0, rainIRQ, FALLING);
attachInterrupt(1, wspeedIRQ, FALLING);
// turn on interrupts
interrupts();
Serial.println("Wimp Weather Station online!");
reportWeather();
// wdt_enable(WDTO_1S); //Unleash the beast
//==========================
}
void loop() {
char message1[120];
char message3[120];
if (millis() - previousMillis >= (interval )) {
//=========================================================================
//================================
//*************CCS811*************
//================================
if(ccs.available()){
float temp = ccs.calculateTemperature();
if(!ccs.readData()){
Serial.print("CO2: ");
Serial.print(ccs.geteCO2());
Serial.print("ppm, TVOC: ");
Serial.print(ccs.getTVOC());
Serial.print("ppb Temp:");
Serial.println(temp);
}
else{
Serial.println("ERROR!");
while(1);
}
}
}
//====================================
//**************MAX30105**************
//===================================
byte ledBrightness = 0xFF; //Options: 0=Off to 255=50mA
byte sampleAverage = 4; //Options: 1, 2, 4, 8, 16, 32
byte ledMode = 3; //Options: 1 = Red only, 2 = Red + IR, 3 = Red + IR + Green
byte sampleRate = 400; //Options: 50, 100, 200, 400, 800, 1000, 1600, 3200
int pulseWidth = 411; //Options: 69, 118, 215, 411
int adcRange = 16384; //Options: 2048, 4096, 8192, 16384
int PROX_INT_EN = 0;
particleSensor.setup(ledBrightness, sampleAverage, ledMode, sampleRate, pulseWidth, adcRange); //Configure sensor with these settings
float temperature = particleSensor.readTemperature();
Serial.print("temperatureC=");
Serial.print(temperature, 4);
///*
debug.print(" R[");
debug.print(particleSensor.getRed());
debug.print("] IR[");
debug.print(particleSensor.getIR());
debug.print("] G[");
debug.print(particleSensor.getGreen());
debug.print("]");
debug.println();
Serial.print("temperatureC=");
Serial.print(temperature, 4);
Serial.println();
//*/
//======================================
//****************BME280****************
//======================================
//Each loop, take a reading.
//Start with temperature, as that data is needed for accurate compensation.
//Reading the temperature updates the compensators of the other functions
//in the background.
///*
Serial.print("Temperature: ");
Serial.print(mySensor.readTempC(), 2);
Serial.println(" degrees C");
Serial.print("Temperature: ");
Serial.print(mySensor.readTempF(), 2);
Serial.println(" degrees F");
Serial.print("Pressure: ");
Serial.print(mySensor.readFloatPressure(), 2);
Serial.println(" Pa");
Serial.print("Altitude: ");
Serial.print(mySensor.readFloatAltitudeMeters(), 2);
Serial.println("m");
Serial.print("Altitude: ");
Serial.print(mySensor.readFloatAltitudeFeet(), 2);
Serial.println("ft");
Serial.print("%RH: ");
Serial.print(mySensor.readFloatHumidity(), 2);
Serial.println(" %");
Serial.println();
//*/
//======================================
//********Lightning_Detector************
//=====================================
if(digitalRead(lightningInt) == HIGH){
// Hardware has alerted us to an event, now we read the interrupt register
// to see exactly what it is.
intVal = lightning.readInterruptReg();
if(intVal == NOISE_INT){
Serial.println("Noise.");
}
else if(intVal == DISTURBER_INT){
Serial.println("Disturber.");
}
else if(intVal == LIGHTNING_INT){
Serial.println("Lightning Strike Detected!");
byte distance = lightning.distanceToStorm();
Serial.print("Approximately: ");
Serial.print(distance);
Serial.println("km away!");
long lightEnergy = lightning.lightningEnergy();
Serial.print("Lightning Energy: ");
Serial.println(lightEnergy);
}
}
//=====================================
//*****Wind_Direction& Speed***********
//=====================================
//Keep track of which minute it is
if(millis() - lastSecond >= 1000)
{
lastSecond += 1000;
//Take a speed and direction reading every second for 2 minute average
if(++seconds_2m > 119) seconds_2m = 0;
//Calc the wind speed and direction every second for 120 second to get 2 minute average
windspeedmph = get_wind_speed();
winddir = get_wind_direction();
windspdavg[seconds_2m] = (int)windspeedmph;
winddiravg[seconds_2m] = winddir;
//if(seconds_2m % 10 == 0) displayArrays();
//Check to see if this is a gust for the minute
if(windspeedmph > windgust_10m[minutes_10m])
{
windgust_10m[minutes_10m] = windspeedmph;
windgustdirection_10m[minutes_10m] = winddir;
}
//Check to see if this is a gust for the day
//Resets at midnight each night
if(windspeedmph > windgustmph)
{
windgustmph = windspeedmph;
windgustdir = winddir;
}
//Blink stat LED briefly to show we are alive
digitalWrite(STAT1, HIGH);
//reportWeather(); //Print the current readings. Takes 172ms.
delay(25);
digitalWrite(STAT1, LOW);
//If we roll over 60 seconds then update the arrays for rain and windgust
if(++seconds > 59)
{
seconds = 0;
if(++minutes > 59) minutes = 0;
if(++minutes_10m > 9) minutes_10m = 0;
rainHour[minutes] = 0; //Zero out this minute's rainfall amount
windgust_10m[minutes_10m] = 0; //Zero out this minute's gust
minutesSinceLastReset++; //It's been another minute since last night's midnight reset
}
}
//Check to see if there's been lighting
#ifdef ENABLE_LIGHTNING
if(digitalRead(LIGHTNING_IRQ) == HIGH)
{
//We've got something!
lightning_distance = readLightning();
}
#endif
//Wait for the imp to ping us with the ! character
if(Serial.available())
{
byte incoming = Serial.read();
if(incoming == '!')
{
reportWeather(); //Send all the current readings out the imp and to its agent for posting to wunderground. Takes 196ms
//Serial.print("Pinged!");
#ifdef ENABLE_LIGHTNING
//Give imp time to transmit then read any erroneous lightning strike
delay(1000); //Give the Imp time to transmit
readLightning(); //Clear any readings and forget it
#endif
}
else if(incoming == '@') //Special character from Imp indicating midnight local time
{
midnightReset(); //Reset a bunch of variables like rain and daily total rain
//Serial.print("Midnight reset");
}
else if(incoming == '#') //Special character from Imp indicating a hardware reset
{
//Serial.print("Watchdog reset");
delay(5000); //This will cause the system to reset because we don't pet the dog
}
}
//If we go for more than 24 hours without a midnight reset then force a reset
//24 hours * 60 mins/hr = 1,440 minutes + 10 extra minutes. We hope that Imp is doing it.
if(minutesSinceLastReset > (1440 + 10))
{
midnightReset(); //Reset a bunch of variables like rain and daily total rain
//Serial.print("Emergency midnight reset");
}
//=====================================
sprintf(message1,"RED%.2d,IR%.2d,GREEN%.2d,Heat_Alarm%f",particleSensor.getRed(),particleSensor.getIR(),particleSensor.getGreen(),temperature);
Particle.publish("Sensor_Array2",message1,PRIVATE);
previousMillis = millis();
}
//Prints the various arrays for debugging
void displayArrays()
{
//Windgusts in this hour
Serial.println();
Serial.print(minutes);
Serial.print(":");
Serial.println(seconds);
Serial.print("Windgust last 10 minutes:");
for(int i = 0 ; i < 10 ; i++)
{
if(i % 10 == 0) Serial.println();
Serial.print(" ");
Serial.print(windgust_10m[i]);
}
//Wind speed avg for past 2 minutes
/*Serial.println();
Serial.print("Wind 2 min avg:");
for(int i = 0 ; i < 120 ; i++)
{
if(i % 30 == 0) Serial.println();
Serial.print(" ");
Serial.print(windspdavg[i]);
}*/
//Rain for last hour
Serial.println();
Serial.print("Rain hour:");
for(int i = 0 ; i < 60 ; i++)
{
if(i % 30 == 0) Serial.println();
Serial.print(" ");
Serial.print(rainHour[i]);
}
}
//When the imp tells us it's midnight, reset the total amount of rain and gusts
void midnightReset()
{
dailyrainin = 0; //Reset daily amount of rain
windgustmph = 0; //Zero out the windgust for the day
windgustdir = 0; //Zero out the gust direction for the day
minutes = 0; //Reset minute tracker
seconds = 0;
lastSecond = millis(); //Reset variable used to track minutes
minutesSinceLastReset = 0; //Zero out the backup midnight reset variable
}
//Calculates each of the variables that wunderground is expecting
void calcWeather()
{
//current winddir, current windspeed, windgustmph, and windgustdir are calculated every 100ms throughout the day
//Calc windspdmph_avg2m
float temp = 0;
for(int i = 0 ; i < 120 ; i++)
temp += windspdavg[i];
temp /= 120.0;
windspdmph_avg2m = temp;
//Calc winddir_avg2m, Wind Direction
//You can't just take the average. Google "mean of circular quantities" for more info
//We will use the Mitsuta method because it doesn't require trig functions
//And because it sounds cool.
//Based on: http://abelian.org/vlf/bearings.html
//Based on: http://stackoverflow.com/questions/1813483/averaging-angles-again
long sum = winddiravg[0];
int D = winddiravg[0];
for(int i = 1 ; i < WIND_DIR_AVG_SIZE ; i++)
{
int delta = winddiravg[i] - D;
if(delta < -180)
D += delta + 360;
else if(delta > 180)
D += delta - 360;
else
D += delta;
sum += D;
}
winddir_avg2m = sum / WIND_DIR_AVG_SIZE;
if(winddir_avg2m >= 360) winddir_avg2m -= 360;
if(winddir_avg2m < 0) winddir_avg2m += 360;
//Calc windgustmph_10m
//Calc windgustdir_10m
//Find the largest windgust in the last 10 minutes
windgustmph_10m = 0;
windgustdir_10m = 0;
//Step through the 10 minutes
for(int i = 0; i < 10 ; i++)
{
if(windgust_10m[i] > windgustmph_10m)
{
windgustmph_10m = windgust_10m[i];
windgustdir_10m = windgustdirection_10m[i];
}
}
light_lvl = get_light_level();
//Calc battery level
batt_lvl = get_battery_level();
//Lightning is checked in the main loop
}
//Returns the voltage of the light sensor based on the 3.3V rail
//This allows us to ignore what VCC might be (an Arduino plugged into USB has VCC of 4.5 to 5.2V)
float get_light_level()
{
float operatingVoltage = averageAnalogRead(REFERENCE_3V3);
float lightSensor = averageAnalogRead(LIGHT);
operatingVoltage = 3.3 / operatingVoltage; //The reference voltage is 3.3V
lightSensor *= operatingVoltage;
return(lightSensor);
}
//Returns the voltage of the raw pin based on the 3.3V rail
//The battery can ranges from 4.2V down to around 3.3V
//This function allows us to ignore what VCC might be (an Arduino plugged into USB has VCC of 4.5 to 5.2V)
//The weather shield has a pin called RAW (VIN) fed through through two 5% resistors and connected to A2 (BATT):
//3.9K on the high side (R1), and 1K on the low side (R2)
float get_battery_level()
{
float operatingVoltage = averageAnalogRead(REFERENCE_3V3);
float rawVoltage = averageAnalogRead(BATT);
operatingVoltage = 3.30 / operatingVoltage; //The reference voltage is 3.3V
rawVoltage *= operatingVoltage; //Convert the 0 to 1023 int to actual voltage on BATT pin
rawVoltage *= 4.90; //(3.9k+1k)/1k - multiply BATT voltage by the voltage divider to get actual system voltage
return(rawVoltage);
}
//Returns the instataneous wind speed
float get_wind_speed()
{
float deltaTime = millis() - lastWindCheck; //750ms
deltaTime /= 1000.0; //Covert to seconds
float windSpeed = (float)windClicks / deltaTime; //3 / 0.750s = 4
windClicks = 0; //Reset and start watching for new wind
lastWindCheck = millis();
windSpeed *= 1.492; //4 * 1.492 = 5.968MPH
Serial.println();
Serial.print("Windspeed:");
Serial.println(windSpeed);
return(windSpeed);
}
int get_wind_direction()
// read the wind direction sensor, return heading in degrees
{
unsigned int adc;
adc = averageAnalogRead(WDIR); // get the current reading from the sensor
// The following table is ADC readings for the wind direction sensor output, sorted from low to high.
// Each threshold is the midpoint between adjacent headings. The output is degrees for that ADC reading.
// Note that these are not in compass degree order! See Weather Meters datasheet for more information.
if (adc < 380) return (113);
if (adc < 393) return (68);
if (adc < 414) return (90);
if (adc < 456) return (158);
if (adc < 508) return (135);
if (adc < 551) return (203);
if (adc < 615) return (180);
if (adc < 680) return (23);
if (adc < 746) return (45);
if (adc < 801) return (248);
if (adc < 833) return (225);
if (adc < 878) return (338);
if (adc < 913) return (0);
if (adc < 940) return (293);
if (adc < 967) return (315);
if (adc < 990) return (270);
return (-1); // error, disconnected?
}
void reportWeather()
{
calcWeather(); //Go calc all the various sensors
Serial.print("$,winddir=");
Serial.print(winddir);
Serial.print(",windspeedmph=");
Serial.print(windspeedmph, 1);
Serial.print(",windgustmph=");
Serial.print(windgustmph, 1);
Serial.print(",windgustdir=");
Serial.print(windgustdir);
Serial.print(",windspdmph_avg2m=");
Serial.print(windspdmph_avg2m, 1);
Serial.print(",winddir_avg2m=");
Serial.print(winddir_avg2m);
Serial.print(",windgustmph_10m=");
Serial.print(windgustmph_10m, 1);
Serial.print(",windgustdir_10m=");
Serial.print(windgustdir_10m);
Serial.print(",humidity=");
Serial.print(humidity, 1);
Serial.print(",tempf=");
Serial.print(tempf, 1);
Serial.print(",rainin=");
Serial.print(rainin, 2);
Serial.print(",dailyrainin=");
Serial.print(dailyrainin, 2);
Serial.print(","); //Don't print pressure= because the agent will be doing calcs on the number
Serial.print(pressure, 2);
Serial.print(",batt_lvl=");
Serial.print(batt_lvl, 2);
Serial.print(",light_lvl=");
Serial.print(light_lvl, 2);
#ifdef LIGHTNING_ENABLED
Serial.print(",lightning_distance=");
Serial.print(lightning_distance);
#endif
Serial.print(",");
Serial.println("#,");
//Test string
Serial.println("$,winddir=270,windspeedmph=0.0,windgustmph=0.0,windgustdir=0,windspdmph_avg2m=0.0,winddir_avg2m=12,windgustmph_10m=0.0,windgustdir_10m=0,humidity=998.0,tempf=-1766.2,rainin=0.00,dailyrainin=0.00,-999.00,batt_lvl=16.11,light_lvl=3.32,#,");
}
int averageAnalogRead(int pinToRead)
{
byte numberOfReadings = 8;
unsigned int runningValue = 0;
for(int x = 0 ; x < numberOfReadings ; x++)
runningValue += analogRead(pinToRead);
runningValue /= numberOfReadings;
return(runningValue);
}
//The following is for the AS3935 lightning sensor
#ifdef ENABLE_LIGHTNING
byte readLightning(void)
{
byte distance = 0;
//Check to see if we have lightning!
if(digitalRead(LIGHTNING_IRQ) == HIGH)
{
// first step is to find out what caused interrupt
// as soon as we read interrupt cause register, irq pin goes low
int irqSource = AS3935.interruptSource();
// returned value is bitmap field, bit 0 - noise level too high, bit 2 - disturber detected, and finally bit 3 - lightning!
if (irqSource & 0b0001)
{
//Serial.println("Noise level too high, try adjusting noise floor");
}
if (irqSource & 0b0100)
{
//Serial.println("Disturber detected");
distance = 64;
}
if (irqSource & 0b1000)
{
// need to find how far that lightning stroke, function returns approximate distance in kilometers,
// where value 1 represents storm in detector's near victinity, and 63 - very distant, out of range stroke
// everything in between is just distance in kilometers
distance = AS3935.lightningDistanceKm();
//Serial.print("Lightning: ");
//Serial.print(lightning_distance, DEC);
//Serial.println(" km");
//The AS3935 remembers the nearest strike distance. For example 15km away then 10, then overhead all following
//distances (10, 20, 30) will instead output as 'Storm overhead, watch out!'. Resetting the chip erases this.
lightning_init();
}
}
return(distance);
}
void startLightning(void)
{
pinMode(slaveSelectPin, OUTPUT); // set the slaveSelectPin as an output:
pinMode(LIGHTNING_IRQ, INPUT_PULLUP); //Set IRQ pin as input
SPI.begin(); //Start SPI
SPI.setDataMode(SPI_MODE1); // NB! chip uses SPI MODE1
SPI.setClockDivider(SPI_CLOCK_DIV16); //Uno 16MHz / 16 = 1MHz
SPI.setBitOrder(MSBFIRST); // and chip is MSB first
lightning_init(); //Setup the values for the sensor
Serial.println("Lightning sensor online");
}
void lightning_init()
{
AS3935.reset(); // reset all internal register values to defaults
// if lightning detector can not tune tank circuit to required tolerance,
// calibration function will return false
if(!AS3935.calibrate())
{
Serial.println("Tuning out of range, check your wiring, your sensor and make sure physics laws have not changed!");
}
AS3935.setOutdoors(); //The weather station is outdoors
AS3935.enableDisturbers(); //We want to know if a man-made event happens
AS3935.setNoiseFloor(3); //See table 16 of the AS3935 datasheet. 4-6 works. This was found through experimentation.
//printAS3935Registers();
}
/*void printAS3935Registers()
{
int noiseFloor = AS3935.getNoiseFloor();
int spikeRejection = AS3935.getSpikeRejection();
int watchdogThreshold = AS3935.getWatchdogThreshold();
Serial.print("Noise floor is: ");
Serial.println(noiseFloor, DEC);
Serial.print("Spike rejection is: ");
Serial.println(spikeRejection, DEC);
Serial.print("Watchdog threshold is: ");
Serial.println(watchdogThreshold, DEC);
}*/
byte SPItransfer(byte sendByte)
{
return SPI.transfer(sendByte);
}
#endif
/***************************************************
This is a library written for the Maxim MAX30105 Optical Smoke Detector
It should also work with the MAX30102. However, the MAX30102 does not have a Green LED.
These sensors use I2C to communicate, as well as a single (optional)
interrupt line that is not currently supported in this driver.
Written by Peter Jansen and Nathan Seidle (SparkFun)
BSD license, all text above must be included in any redistribution.
*****************************************************/
#include "MAX30105.h"
// Status Registers
static const uint8_t MAX30105_INTSTAT1 = 0x00;
static const uint8_t MAX30105_INTSTAT2 = 0x01;
static const uint8_t MAX30105_INTENABLE1 = 0x02;
static const uint8_t MAX30105_INTENABLE2 = 0x03;
// FIFO Registers
static const uint8_t MAX30105_FIFOWRITEPTR = 0x04;
static const uint8_t MAX30105_FIFOOVERFLOW = 0x05;
static const uint8_t MAX30105_FIFOREADPTR = 0x06;
static const uint8_t MAX30105_FIFODATA = 0x07;
// Configuration Registers
static const uint8_t MAX30105_FIFOCONFIG = 0x08;
static const uint8_t MAX30105_MODECONFIG = 0x09;
static const uint8_t MAX30105_PARTICLECONFIG = 0x0A; // Note, sometimes listed as "SPO2" config in datasheet (pg. 11)
static const uint8_t MAX30105_LED1_PULSEAMP = 0x0C;
static const uint8_t MAX30105_LED2_PULSEAMP = 0x0D;
static const uint8_t MAX30105_LED3_PULSEAMP = 0x0E;
static const uint8_t MAX30105_LED_PROX_AMP = 0x10;
static const uint8_t MAX30105_MULTILEDCONFIG1 = 0x11;
static const uint8_t MAX30105_MULTILEDCONFIG2 = 0x12;
// Die Temperature Registers
static const uint8_t MAX30105_DIETEMPINT = 0x1F;
static const uint8_t MAX30105_DIETEMPFRAC = 0x20;
static const uint8_t MAX30105_DIETEMPCONFIG = 0x21;
// Proximity Function Registers
static const uint8_t MAX30105_PROXINTTHRESH = 0x30;
// Part ID Registers
static const uint8_t MAX30105_REVISIONID = 0xFE;
static const uint8_t MAX30105_PARTID = 0xFF; // Should always be 0x15. Identical to MAX30102.
// MAX30105 Commands
// Interrupt configuration (pg 13, 14)
static const uint8_t MAX30105_INT_A_FULL_MASK = (byte)~0b10000000;
static const uint8_t MAX30105_INT_A_FULL_ENABLE = 0x80;
static const uint8_t MAX30105_INT_A_FULL_DISABLE = 0x00;
static const uint8_t MAX30105_INT_DATA_RDY_MASK = (byte)~0b01000000;
static const uint8_t MAX30105_INT_DATA_RDY_ENABLE = 0x40;
static const uint8_t MAX30105_INT_DATA_RDY_DISABLE = 0x00;
static const uint8_t MAX30105_INT_ALC_OVF_MASK = (byte)~0b00100000;
static const uint8_t MAX30105_INT_ALC_OVF_ENABLE = 0x20;
static const uint8_t MAX30105_INT_ALC_OVF_DISABLE = 0x00;
static const uint8_t MAX30105_INT_PROX_INT_MASK = (byte)~0b00010000;
static const uint8_t MAX30105_INT_PROX_INT_ENABLE = 0x10;
static const uint8_t MAX30105_INT_PROX_INT_DISABLE = 0x00;
static const uint8_t MAX30105_INT_DIE_TEMP_RDY_MASK = (byte)~0b00000010;
static const uint8_t MAX30105_INT_DIE_TEMP_RDY_ENABLE = 0x02;
static const uint8_t MAX30105_INT_DIE_TEMP_RDY_DISABLE = 0x00;
static const uint8_t MAX30105_SAMPLEAVG_MASK = (byte)~0b11100000;
static const uint8_t MAX30105_SAMPLEAVG_1 = 0x00;
static const uint8_t MAX30105_SAMPLEAVG_2 = 0x20;
static const uint8_t MAX30105_SAMPLEAVG_4 = 0x40;
static const uint8_t MAX30105_SAMPLEAVG_8 = 0x60;
static const uint8_t MAX30105_SAMPLEAVG_16 = 0x80;
static const uint8_t MAX30105_SAMPLEAVG_32 = 0xA0;
static const uint8_t MAX30105_ROLLOVER_MASK = 0xEF;
static const uint8_t MAX30105_ROLLOVER_ENABLE = 0x10;
static const uint8_t MAX30105_ROLLOVER_DISABLE = 0x00;
static const uint8_t MAX30105_A_FULL_MASK = 0xF0;
// Mode configuration commands (page 19)
static const uint8_t MAX30105_SHUTDOWN_MASK = 0x7F;
static const uint8_t MAX30105_SHUTDOWN = 0x80;
static const uint8_t MAX30105_WAKEUP = 0x00;
static const uint8_t MAX30105_RESET_MASK = 0xBF;
static const uint8_t MAX30105_RESET = 0x40;
static const uint8_t MAX30105_MODE_MASK = 0xF8;
static const uint8_t MAX30105_MODE_REDONLY = 0x02;
static const uint8_t MAX30105_MODE_REDIRONLY = 0x03;
static const uint8_t MAX30105_MODE_MULTILED = 0x07;
// Particle sensing configuration commands (pgs 19-20)
static const uint8_t MAX30105_ADCRANGE_MASK = 0x9F;
static const uint8_t MAX30105_ADCRANGE_2048 = 0x00;
static const uint8_t MAX30105_ADCRANGE_4096 = 0x20;
static const uint8_t MAX30105_ADCRANGE_8192 = 0x40;
static const uint8_t MAX30105_ADCRANGE_16384 = 0x60;
static const uint8_t MAX30105_SAMPLERATE_MASK = 0xE3;
static const uint8_t MAX30105_SAMPLERATE_50 = 0x00;
static const uint8_t MAX30105_SAMPLERATE_100 = 0x04;
static const uint8_t MAX30105_SAMPLERATE_200 = 0x08;
static const uint8_t MAX30105_SAMPLERATE_400 = 0x0C;
static const uint8_t MAX30105_SAMPLERATE_800 = 0x10;
static const uint8_t MAX30105_SAMPLERATE_1000 = 0x14;
static const uint8_t MAX30105_SAMPLERATE_1600 = 0x18;
static const uint8_t MAX30105_SAMPLERATE_3200 = 0x1C;
static const uint8_t MAX30105_PULSEWIDTH_MASK = 0xFC;
static const uint8_t MAX30105_PULSEWIDTH_69 = 0x00;
static const uint8_t MAX30105_PULSEWIDTH_118 = 0x01;
static const uint8_t MAX30105_PULSEWIDTH_215 = 0x02;
static const uint8_t MAX30105_PULSEWIDTH_411 = 0x03;
//Multi-LED Mode configuration (pg 22)
static const uint8_t MAX30105_SLOT1_MASK = 0xF8;
static const uint8_t MAX30105_SLOT2_MASK = 0x8F;
static const uint8_t MAX30105_SLOT3_MASK = 0xF8;
static const uint8_t MAX30105_SLOT4_MASK = 0x8F;
static const uint8_t SLOT_NONE = 0x00;
static const uint8_t SLOT_RED_LED = 0x01;
static const uint8_t SLOT_IR_LED = 0x02;
static const uint8_t SLOT_GREEN_LED = 0x03;
static const uint8_t SLOT_NONE_PILOT = 0x04;
static const uint8_t SLOT_RED_PILOT = 0x05;
static const uint8_t SLOT_IR_PILOT = 0x06;
static const uint8_t SLOT_GREEN_PILOT = 0x07;
static const uint8_t MAX_30105_EXPECTEDPARTID = 0x15;
MAX30105::MAX30105() {
// Constructor
}
boolean MAX30105::begin(TwoWire &wirePort, uint32_t i2cSpeed, uint8_t i2caddr) {
//int setClock = i2cSpeed;
_i2cPort = &wirePort; //Grab which port the user wants us to use
_i2cPort->begin();
// _i2cPort->setClock(i2cSpeed);
_i2caddr = i2caddr;
// Step 1: Initial Communication and Verification
// Check that a MAX30105 is connected
if (readPartID() != MAX_30105_EXPECTEDPARTID) {
// Error -- Part ID read from MAX30105 does not match expected part ID.
// This may mean there is a physical connectivity problem (broken wire, unpowered, etc).
return false;
}
// Populate revision ID
readRevisionID();
return true;
}
//
// Configuration
//
//Begin Interrupt configuration
uint8_t MAX30105::getINT1(void) {
return (readRegister8(_i2caddr, MAX30105_INTSTAT1));
}
uint8_t MAX30105::getINT2(void) {
return (readRegister8(_i2caddr, MAX30105_INTSTAT2));
}
void MAX30105::enableAFULL(void) {
bitMask(MAX30105_INTENABLE1, MAX30105_INT_A_FULL_MASK, MAX30105_INT_A_FULL_ENABLE);
}
void MAX30105::disableAFULL(void) {
bitMask(MAX30105_INTENABLE1, MAX30105_INT_A_FULL_MASK, MAX30105_INT_A_FULL_DISABLE);
}
void MAX30105::enableDATARDY(void) {
bitMask(MAX30105_INTENABLE1, MAX30105_INT_DATA_RDY_MASK, MAX30105_INT_DATA_RDY_ENABLE);
}
void MAX30105::disableDATARDY(void) {
bitMask(MAX30105_INTENABLE1, MAX30105_INT_DATA_RDY_MASK, MAX30105_INT_DATA_RDY_DISABLE);
}
void MAX30105::enableALCOVF(void) {
bitMask(MAX30105_INTENABLE1, MAX30105_INT_ALC_OVF_MASK, MAX30105_INT_ALC_OVF_ENABLE);
}
void MAX30105::disableALCOVF(void) {
bitMask(MAX30105_INTENABLE1, MAX30105_INT_ALC_OVF_MASK, MAX30105_INT_ALC_OVF_DISABLE);
}
void MAX30105::enablePROXINT(void) {
bitMask(MAX30105_INTENABLE1, MAX30105_INT_PROX_INT_MASK, MAX30105_INT_PROX_INT_ENABLE);
}
void MAX30105::disablePROXINT(void) {
bitMask(MAX30105_INTENABLE1, MAX30105_INT_PROX_INT_MASK, MAX30105_INT_PROX_INT_DISABLE);
}
void MAX30105::enableDIETEMPRDY(void) {
bitMask(MAX30105_INTENABLE2, MAX30105_INT_DIE_TEMP_RDY_MASK, MAX30105_INT_DIE_TEMP_RDY_ENABLE);
}
void MAX30105::disableDIETEMPRDY(void) {
bitMask(MAX30105_INTENABLE2, MAX30105_INT_DIE_TEMP_RDY_MASK, MAX30105_INT_DIE_TEMP_RDY_DISABLE);
}
//End Interrupt configuration
void MAX30105::softReset(void) {
bitMask(MAX30105_MODECONFIG, MAX30105_RESET_MASK, MAX30105_RESET);
// Poll for bit to clear, reset is then complete
// Timeout after 100ms
unsigned long startTime = millis();
while (millis() - startTime < 100)
{
uint8_t response = readRegister8(_i2caddr, MAX30105_MODECONFIG);
if ((response & MAX30105_RESET) == 0) break; //We're done!
delay(1); //Let's not over burden the I2C bus
}
}
void MAX30105::shutDown(void) {
// Put IC into low power mode (datasheet pg. 19)
// During shutdown the IC will continue to respond to I2C commands but will
// not update with or take new readings (such as temperature)
bitMask(MAX30105_MODECONFIG, MAX30105_SHUTDOWN_MASK, MAX30105_SHUTDOWN);
}
void MAX30105::wakeUp(void) {
// Pull IC out of low power mode (datasheet pg. 19)
bitMask(MAX30105_MODECONFIG, MAX30105_SHUTDOWN_MASK, MAX30105_WAKEUP);
}
void MAX30105::setLEDMode(uint8_t mode) {
// Set which LEDs are used for sampling -- Red only, RED+IR only, or custom.
// See datasheet, page 19
bitMask(MAX30105_MODECONFIG, MAX30105_MODE_MASK, mode);
}
void MAX30105::setADCRange(uint8_t adcRange) {
// adcRange: one of MAX30105_ADCRANGE_2048, _4096, _8192, _16384
bitMask(MAX30105_PARTICLECONFIG, MAX30105_ADCRANGE_MASK, adcRange);
}
void MAX30105::setSampleRate(uint8_t sampleRate) {
// sampleRate: one of MAX30105_SAMPLERATE_50, _100, _200, _400, _800, _1000, _1600, _3200
bitMask(MAX30105_PARTICLECONFIG, MAX30105_SAMPLERATE_MASK, sampleRate);
}
void MAX30105::setPulseWidth(uint8_t pulseWidth) {
// pulseWidth: one of MAX30105_PULSEWIDTH_69, _188, _215, _411
bitMask(MAX30105_PARTICLECONFIG, MAX30105_PULSEWIDTH_MASK, pulseWidth);
}
// NOTE: Amplitude values: 0x00 = 0mA, 0x7F = 25.4mA, 0xFF = 50mA (typical)
// See datasheet, page 21
void MAX30105::setPulseAmplitudeRed(uint8_t amplitude) {
writeRegister8(_i2caddr, MAX30105_LED1_PULSEAMP, amplitude);
}
void MAX30105::setPulseAmplitudeIR(uint8_t amplitude) {
writeRegister8(_i2caddr, MAX30105_LED2_PULSEAMP, amplitude);
}
void MAX30105::setPulseAmplitudeGreen(uint8_t amplitude) {
writeRegister8(_i2caddr, MAX30105_LED3_PULSEAMP, amplitude);
}
void MAX30105::setPulseAmplitudeProximity(uint8_t amplitude) {
writeRegister8(_i2caddr, MAX30105_LED_PROX_AMP, amplitude);
}
void MAX30105::setProximityThreshold(uint8_t threshMSB) {
// Set the IR ADC count that will trigger the beginning of particle-sensing mode.
// The threshMSB signifies only the 8 most significant-bits of the ADC count.
// See datasheet, page 24.
writeRegister8(_i2caddr, MAX30105_PROXINTTHRESH, threshMSB);
}
//Given a slot number assign a thing to it
//Devices are SLOT_RED_LED or SLOT_RED_PILOT (proximity)
//Assigning a SLOT_RED_LED will pulse LED
//Assigning a SLOT_RED_PILOT will ??
void MAX30105::enableSlot(uint8_t slotNumber, uint8_t device) {
uint8_t originalContents;
switch (slotNumber) {
case (1):
bitMask(MAX30105_MULTILEDCONFIG1, MAX30105_SLOT1_MASK, device);
break;
case (2):
bitMask(MAX30105_MULTILEDCONFIG1, MAX30105_SLOT2_MASK, device << 4);
break;
case (3):
bitMask(MAX30105_MULTILEDCONFIG2, MAX30105_SLOT3_MASK, device);
break;
case (4):
bitMask(MAX30105_MULTILEDCONFIG2, MAX30105_SLOT4_MASK, device << 4);
break;
default:
//Shouldn't be here!
break;
}
}
//Clears all slot assignments
void MAX30105::disableSlots(void) {
writeRegister8(_i2caddr, MAX30105_MULTILEDCONFIG1, 0);
writeRegister8(_i2caddr, MAX30105_MULTILEDCONFIG2, 0);
}
//
// FIFO Configuration
//
//Set sample average (Table 3, Page 18)
void MAX30105::setFIFOAverage(uint8_t numberOfSamples) {
bitMask(MAX30105_FIFOCONFIG, MAX30105_SAMPLEAVG_MASK, numberOfSamples);
}
//Resets all points to start in a known state
//Page 15 recommends clearing FIFO before beginning a read
void MAX30105::clearFIFO(void) {
writeRegister8(_i2caddr, MAX30105_FIFOWRITEPTR, 0);
writeRegister8(_i2caddr, MAX30105_FIFOOVERFLOW, 0);
writeRegister8(_i2caddr, MAX30105_FIFOREADPTR, 0);
}
//Enable roll over if FIFO over flows
void MAX30105::enableFIFORollover(void) {
bitMask(MAX30105_FIFOCONFIG, MAX30105_ROLLOVER_MASK, MAX30105_ROLLOVER_ENABLE);
}
//Disable roll over if FIFO over flows
void MAX30105::disableFIFORollover(void) {
bitMask(MAX30105_FIFOCONFIG, MAX30105_ROLLOVER_MASK, MAX30105_ROLLOVER_DISABLE);
}
//Set number of samples to trigger the almost full interrupt (Page 18)
//Power on default is 32 samples
//Note it is reverse: 0x00 is 32 samples, 0x0F is 17 samples
void MAX30105::setFIFOAlmostFull(uint8_t numberOfSamples) {
bitMask(MAX30105_FIFOCONFIG, MAX30105_A_FULL_MASK, numberOfSamples);
}
//Read the FIFO Write Pointer
uint8_t MAX30105::getWritePointer(void) {
return (readRegister8(_i2caddr, MAX30105_FIFOWRITEPTR));
}
//Read the FIFO Read Pointer
uint8_t MAX30105::getReadPointer(void) {
return (readRegister8(_i2caddr, MAX30105_FIFOREADPTR));
}
// Die Temperature
// Returns temp in C
float MAX30105::readTemperature() {
//DIE_TEMP_RDY interrupt must be enabled
//See issue 19: https://github.com/sparkfun/SparkFun_MAX3010x_Sensor_Library/issues/19
// Step 1: Config die temperature register to take 1 temperature sample
writeRegister8(_i2caddr, MAX30105_DIETEMPCONFIG, 0x01);
// Poll for bit to clear, reading is then complete
// Timeout after 100ms
unsigned long startTime = millis();
while (millis() - startTime < 100)
{
//uint8_t response = readRegister8(_i2caddr, MAX30105_DIETEMPCONFIG); //Original way
//if ((response & 0x01) == 0) break; //We're done!
//Check to see if DIE_TEMP_RDY interrupt is set
uint8_t response = readRegister8(_i2caddr, MAX30105_INTSTAT2);
if ((response & MAX30105_INT_DIE_TEMP_RDY_ENABLE) > 0) break; //We're done!
delay(1); //Let's not over burden the I2C bus
}
//TODO How do we want to fail? With what type of error?
//? if(millis() - startTime >= 100) return(-999.0);
// Step 2: Read die temperature register (integer)
int8_t tempInt = readRegister8(_i2caddr, MAX30105_DIETEMPINT);
uint8_t tempFrac = readRegister8(_i2caddr, MAX30105_DIETEMPFRAC); //Causes the clearing of the DIE_TEMP_RDY interrupt
// Step 3: Calculate temperature (datasheet pg. 23)
return (float)tempInt + ((float)tempFrac * 0.0625);
}
// Returns die temp in F
float MAX30105::readTemperatureF() {
float temp = readTemperature();
if (temp != -999.0) temp = temp * 1.8 + 32.0;
return (temp);
}
// Set the PROX_INT_THRESHold
void MAX30105::setPROXINTTHRESH(uint8_t val) {
writeRegister8(_i2caddr, MAX30105_PROXINTTHRESH, val);
}
//
// Device ID and Revision
//
uint8_t MAX30105::readPartID() {
return readRegister8(_i2caddr, MAX30105_PARTID);
}
void MAX30105::readRevisionID() {
revisionID = readRegister8(_i2caddr, MAX30105_REVISIONID);
}
uint8_t MAX30105::getRevisionID() {
return revisionID;
}
//Setup the sensor
//The MAX30105 has many settings. By default we select:
// Sample Average = 4
// Mode = MultiLED
// ADC Range = 16384 (62.5pA per LSB)
// Sample rate = 50
//Use the default setup if you are just getting started with the MAX30105 sensor
void MAX30105::setup(byte powerLevel, byte sampleAverage, byte ledMode, int sampleRate, int pulseWidth, int adcRange) {
softReset(); //Reset all configuration, threshold, and data registers to POR values
//FIFO Configuration
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
//The chip will average multiple samples of same type together if you wish
if (sampleAverage == 1) setFIFOAverage(MAX30105_SAMPLEAVG_1); //No averaging per FIFO record
else if (sampleAverage == 2) setFIFOAverage(MAX30105_SAMPLEAVG_2);
else if (sampleAverage == 4) setFIFOAverage(MAX30105_SAMPLEAVG_4);
else if (sampleAverage == 8) setFIFOAverage(MAX30105_SAMPLEAVG_8);
else if (sampleAverage == 16) setFIFOAverage(MAX30105_SAMPLEAVG_16);
else if (sampleAverage == 32) setFIFOAverage(MAX30105_SAMPLEAVG_32);
else setFIFOAverage(MAX30105_SAMPLEAVG_4);
//setFIFOAlmostFull(2); //Set to 30 samples to trigger an 'Almost Full' interrupt
enableFIFORollover(); //Allow FIFO to wrap/roll over
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
//Mode Configuration
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
if (ledMode == 3) setLEDMode(MAX30105_MODE_MULTILED); //Watch all three LED channels
else if (ledMode == 2) setLEDMode(MAX30105_MODE_REDIRONLY); //Red and IR
else setLEDMode(MAX30105_MODE_REDONLY); //Red only
activeLEDs = ledMode; //Used to control how many bytes to read from FIFO buffer
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
//Particle Sensing Configuration
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
if(adcRange < 4096) setADCRange(MAX30105_ADCRANGE_2048); //7.81pA per LSB
else if(adcRange < 8192) setADCRange(MAX30105_ADCRANGE_4096); //15.63pA per LSB
else if(adcRange < 16384) setADCRange(MAX30105_ADCRANGE_8192); //31.25pA per LSB
else if(adcRange == 16384) setADCRange(MAX30105_ADCRANGE_16384); //62.5pA per LSB
else setADCRange(MAX30105_ADCRANGE_2048);
if (sampleRate < 100) setSampleRate(MAX30105_SAMPLERATE_50); //Take 50 samples per second
else if (sampleRate < 200) setSampleRate(MAX30105_SAMPLERATE_100);
else if (sampleRate < 400) setSampleRate(MAX30105_SAMPLERATE_200);
else if (sampleRate < 800) setSampleRate(MAX30105_SAMPLERATE_400);
else if (sampleRate < 1000) setSampleRate(MAX30105_SAMPLERATE_800);
else if (sampleRate < 1600) setSampleRate(MAX30105_SAMPLERATE_1000);
else if (sampleRate < 3200) setSampleRate(MAX30105_SAMPLERATE_1600);
else if (sampleRate == 3200) setSampleRate(MAX30105_SAMPLERATE_3200);
else setSampleRate(MAX30105_SAMPLERATE_50);
//The longer the pulse width the longer range of detection you'll have
//At 69us and 0.4mA it's about 2 inches
//At 411us and 0.4mA it's about 6 inches
if (pulseWidth < 118) setPulseWidth(MAX30105_PULSEWIDTH_69); //Page 26, Gets us 15 bit resolution
else if (pulseWidth < 215) setPulseWidth(MAX30105_PULSEWIDTH_118); //16 bit resolution
else if (pulseWidth < 411) setPulseWidth(MAX30105_PULSEWIDTH_215); //17 bit resolution
else if (pulseWidth == 411) setPulseWidth(MAX30105_PULSEWIDTH_411); //18 bit resolution
else setPulseWidth(MAX30105_PULSEWIDTH_69);
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
//LED Pulse Amplitude Configuration
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
//Default is 0x1F which gets us 6.4mA
//powerLevel = 0x02, 0.4mA - Presence detection of ~4 inch
//powerLevel = 0x1F, 6.4mA - Presence detection of ~8 inch
//powerLevel = 0x7F, 25.4mA - Presence detection of ~8 inch
//powerLevel = 0xFF, 50.0mA - Presence detection of ~12 inch
setPulseAmplitudeRed(powerLevel);
setPulseAmplitudeIR(powerLevel);
setPulseAmplitudeGreen(powerLevel);
setPulseAmplitudeProximity(powerLevel);
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
//Multi-LED Mode Configuration, Enable the reading of the three LEDs
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
enableSlot(1, SLOT_RED_LED);
if (ledMode > 1) enableSlot(2, SLOT_IR_LED);
if (ledMode > 2) enableSlot(3, SLOT_GREEN_LED);
//enableSlot(1, SLOT_RED_PILOT);
//enableSlot(2, SLOT_IR_PILOT);
//enableSlot(3, SLOT_GREEN_PILOT);
//-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
clearFIFO(); //Reset the FIFO before we begin checking the sensor
}
//
// Data Collection
//
//Tell caller how many samples are available
uint8_t MAX30105::available(void)
{
int8_t numberOfSamples = sense.head - sense.tail;
if (numberOfSamples < 0) numberOfSamples += STORAGE_SIZE;
return (numberOfSamples);
}
//Report the most recent red value
uint32_t MAX30105::getRed(void)
{
//Check the sensor for new data for 250ms
if(safeCheck(250))
return (sense.red[sense.head]);
else
return(0); //Sensor failed to find new data
}
//Report the most recent IR value
uint32_t MAX30105::getIR(void)
{
//Check the sensor for new data for 250ms
if(safeCheck(250))
return (sense.IR[sense.head]);
else
return(0); //Sensor failed to find new data
}
//Report the most recent Green value
uint32_t MAX30105::getGreen(void)
{
//Check the sensor for new data for 250ms
if(safeCheck(250))
return (sense.green[sense.head]);
else
return(0); //Sensor failed to find new data
}
//Report the next Red value in the FIFO
uint32_t MAX30105::getFIFORed(void)
{
return (sense.red[sense.tail]);
}
//Report the next IR value in the FIFO
uint32_t MAX30105::getFIFOIR(void)
{
return (sense.IR[sense.tail]);
}
//Report the next Green value in the FIFO
uint32_t MAX30105::getFIFOGreen(void)
{
return (sense.green[sense.tail]);
}
//Advance the tail
void MAX30105::nextSample(void)
{
if(available()) //Only advance the tail if new data is available
{
sense.tail++;
sense.tail %= STORAGE_SIZE; //Wrap condition
}
}
//Polls the sensor for new data
//Call regularly
//If new data is available, it updates the head and tail in the main struct
//Returns number of new samples obtained
uint16_t MAX30105::check(void)
{
//Read register FIDO_DATA in (3-byte * number of active LED) chunks
//Until FIFO_RD_PTR = FIFO_WR_PTR
byte readPointer = getReadPointer();
byte writePointer = getWritePointer();
int numberOfSamples = 0;
//Do we have new data?
if (readPointer != writePointer)
{
//Calculate the number of readings we need to get from sensor
numberOfSamples = writePointer - readPointer;
if (numberOfSamples < 0) numberOfSamples += 32; //Wrap condition
//We now have the number of readings, now calc bytes to read
//For this example we are just doing Red and IR (3 bytes each)
int bytesLeftToRead = numberOfSamples * activeLEDs * 3;
//Get ready to read a burst of data from the FIFO register
_i2cPort->beginTransmission(MAX30105_ADDRESS);
_i2cPort->write(MAX30105_FIFODATA);
_i2cPort->endTransmission();
//We may need to read as many as 288 bytes so we read in blocks no larger than I2C_BUFFER_LENGTH
//I2C_BUFFER_LENGTH changes based on the platform. 64 bytes for SAMD21, 32 bytes for Uno.
//Wire.requestFrom() is limited to BUFFER_LENGTH which is 32 on the Uno
while (bytesLeftToRead > 0)
{
int toGet = bytesLeftToRead;
if (toGet > I2C_BUFFER_LENGTH)
{
//If toGet is 32 this is bad because we read 6 bytes (Red+IR * 3 = 6) at a time
//32 % 6 = 2 left over. We don't want to request 32 bytes, we want to request 30.
//32 % 9 (Red+IR+GREEN) = 5 left over. We want to request 27.
toGet = I2C_BUFFER_LENGTH - (I2C_BUFFER_LENGTH % (activeLEDs * 3)); //Trim toGet to be a multiple of the samples we need to read
}
bytesLeftToRead -= toGet;
//Request toGet number of bytes from sensor
_i2cPort->requestFrom(MAX30105_ADDRESS, toGet);
while (toGet > 0)
{
sense.head++; //Advance the head of the storage struct
sense.head %= STORAGE_SIZE; //Wrap condition
byte temp[sizeof(uint32_t)]; //Array of 4 bytes that we will convert into long
uint32_t tempLong;
//Burst read three bytes - RED
temp[3] = 0;
temp[2] = _i2cPort->read();
temp[1] = _i2cPort->read();
temp[0] = _i2cPort->read();
//Convert array to long
memcpy(&tempLong, temp, sizeof(tempLong));
tempLong &= 0x3FFFF; //Zero out all but 18 bits
sense.red[sense.head] = tempLong; //Store this reading into the sense array
if (activeLEDs > 1)
{
//Burst read three more bytes - IR
temp[3] = 0;
temp[2] = _i2cPort->read();
temp[1] = _i2cPort->read();
temp[0] = _i2cPort->read();
//Convert array to long
memcpy(&tempLong, temp, sizeof(tempLong));
tempLong &= 0x3FFFF; //Zero out all but 18 bits
sense.IR[sense.head] = tempLong;
}
if (activeLEDs > 2)
{
//Burst read three more bytes - Green
temp[3] = 0;
temp[2] = _i2cPort->read();
temp[1] = _i2cPort->read();
temp[0] = _i2cPort->read();
//Convert array to long
memcpy(&tempLong, temp, sizeof(tempLong));
tempLong &= 0x3FFFF; //Zero out all but 18 bits
sense.green[sense.head] = tempLong;
}
toGet -= activeLEDs * 3;
}
} //End while (bytesLeftToRead > 0)
} //End readPtr != writePtr
return (numberOfSamples); //Let the world know how much new data we found
}
//Check for new data but give up after a certain amount of time
//Returns true if new data was found
//Returns false if new data was not found
bool MAX30105::safeCheck(uint8_t maxTimeToCheck)
{
uint32_t markTime = millis();
while(1)
{
if(millis() - markTime > maxTimeToCheck) return(false);
if(check() == true) //We found new data!
return(true);
delay(1);
}
}
//Given a register, read it, mask it, and then set the thing
void MAX30105::bitMask(uint8_t reg, uint8_t mask, uint8_t thing)
{
// Grab current register context
uint8_t originalContents = readRegister8(_i2caddr, reg);
// Zero-out the portions of the register we're interested in
originalContents = originalContents & mask;
// Change contents
writeRegister8(_i2caddr, reg, originalContents | thing);
}
//
// Low-level I2C Communication
//
uint8_t MAX30105::readRegister8(uint8_t address, uint8_t reg) {
_i2cPort->beginTransmission(address);
_i2cPort->write(reg);
_i2cPort->endTransmission(false);
_i2cPort->requestFrom((uint8_t)address, (uint8_t)1); // Request 1 byte
if (_i2cPort->available())
{
return(_i2cPort->read());
}
return (0); //Fail
}
void MAX30105::writeRegister8(uint8_t address, uint8_t reg, uint8_t value) {
_i2cPort->beginTransmission(address);
_i2cPort->write(reg);
_i2cPort->write(value);
_i2cPort->endTransmission();
}
/***************************************************
This is a library written for the Maxim MAX30105 Optical Smoke Detector
It should also work with the MAX30102. However, the MAX30102 does not have a Green LED.
These sensors use I2C to communicate, as well as a single (optional)
interrupt line that is not currently supported in this driver.
Written by Peter Jansen and Nathan Seidle (SparkFun)
BSD license, all text above must be included in any redistribution.
*****************************************************/
#pragma once
#include "Particle.h"
#define MAX30105_ADDRESS 0x57 //7-bit I2C Address
//Note that MAX30102 has the same I2C address and Part ID
#define I2C_SPEED_STANDARD 100000
#define I2C_SPEED_FAST 400000
//Define the size of the I2C buffer based on the platform the user has
#if defined(__AVR_ATmega328P__) || defined(__AVR_ATmega168__)
//I2C_BUFFER_LENGTH is defined in Wire.H
#define I2C_BUFFER_LENGTH BUFFER_LENGTH
#elif defined(__SAMD21G18A__)
//SAMD21 uses RingBuffer.h
#define I2C_BUFFER_LENGTH SERIAL_BUFFER_SIZE
#else
//The catch-all default is 32
#define I2C_BUFFER_LENGTH 32
#endif
class MAX30105 {
public:
MAX30105(void);
boolean begin(TwoWire &wirePort = Wire, uint32_t i2cSpeed = I2C_SPEED_STANDARD, uint8_t i2caddr = MAX30105_ADDRESS);
uint32_t getRed(void); //Returns immediate red value
uint32_t getIR(void); //Returns immediate IR value
uint32_t getGreen(void); //Returns immediate green value
bool safeCheck(uint8_t maxTimeToCheck); //Given a max amount of time, check for new data
// Configuration
void softReset();
void shutDown();
void wakeUp();
void setLEDMode(uint8_t mode);
void setADCRange(uint8_t adcRange);
void setSampleRate(uint8_t sampleRate);
void setPulseWidth(uint8_t pulseWidth);
void setPulseAmplitudeRed(uint8_t value);
void setPulseAmplitudeIR(uint8_t value);
void setPulseAmplitudeGreen(uint8_t value);
void setPulseAmplitudeProximity(uint8_t value);
void setProximityThreshold(uint8_t threshMSB);
//Multi-led configuration mode (page 22)
void enableSlot(uint8_t slotNumber, uint8_t device); //Given slot number, assign a device to slot
void disableSlots(void);
// Data Collection
//Interrupts (page 13, 14)
uint8_t getINT1(void); //Returns the main interrupt group
uint8_t getINT2(void); //Returns the temp ready interrupt
void enableAFULL(void); //Enable/disable individual interrupts
void disableAFULL(void);
void enableDATARDY(void);
void disableDATARDY(void);
void enableALCOVF(void);
void disableALCOVF(void);
void enablePROXINT(void);
void disablePROXINT(void);
void enableDIETEMPRDY(void);
void disableDIETEMPRDY(void);
//FIFO Configuration (page 18)
void setFIFOAverage(uint8_t samples);
void enableFIFORollover();
void disableFIFORollover();
void setFIFOAlmostFull(uint8_t samples);
//FIFO Reading
uint16_t check(void); //Checks for new data and fills FIFO
uint8_t available(void); //Tells caller how many new samples are available (head - tail)
void nextSample(void); //Advances the tail of the sense array
uint32_t getFIFORed(void); //Returns the FIFO sample pointed to by tail
uint32_t getFIFOIR(void); //Returns the FIFO sample pointed to by tail
uint32_t getFIFOGreen(void); //Returns the FIFO sample pointed to by tail
uint8_t getWritePointer(void);
uint8_t getReadPointer(void);
void clearFIFO(void); //Sets the read/write pointers to zero
//Proximity Mode Interrupt Threshold
void setPROXINTTHRESH(uint8_t val);
// Die Temperature
float readTemperature();
float readTemperatureF();
// Detecting ID/Revision
uint8_t getRevisionID();
uint8_t readPartID();
// Setup the IC with user selectable settings
void setup(byte powerLevel = 0x1F, byte sampleAverage = 4, byte ledMode = 3, int sampleRate = 400, int pulseWidth = 411, int adcRange = 4096);
// Low-level I2C communication
uint8_t readRegister8(uint8_t address, uint8_t reg);
void writeRegister8(uint8_t address, uint8_t reg, uint8_t value);
private:
TwoWire *_i2cPort; //The generic connection to user's chosen I2C hardware
uint8_t _i2caddr;
//activeLEDs is the number of channels turned on, and can be 1 to 3. 2 is common for Red+IR.
byte activeLEDs; //Gets set during setup. Allows check() to calculate how many bytes to read from FIFO
uint8_t revisionID;
void readRevisionID();
void bitMask(uint8_t reg, uint8_t mask, uint8_t thing);
#define STORAGE_SIZE 4 //Each long is 4 bytes so limit this to fit on your micro
typedef struct Record
{
uint32_t red[STORAGE_SIZE];
uint32_t IR[STORAGE_SIZE];
uint32_t green[STORAGE_SIZE];
byte head;
byte tail;
} sense_struct; //This is our circular buffer of readings from the sensor
sense_struct sense;
};
#ifndef _SPARKFUN_AS3935_H_
#define _SPARKFUN_AS3935_H_
#include <Wire.h>
#include <SPI.h>
#include <Arduino.h>
typedef uint8_t i2cAddress;
const i2cAddress defAddr = 0x03; // Default ADD0 and ADD1 are HIGH
const i2cAddress addrOneHigh = 0x02; // ADD1 HIGH, ADD0 LOW
const i2cAddress addrZeroHigh = 0x01;// ADD1 LOW, ADD0 HIGH
enum SF_AS3935_REGISTER_NAMES {
AFE_GAIN = 0x00,
THRESHOLD,
LIGHTNING_REG,
INT_MASK_ANT,
ENERGY_LIGHT_LSB,
ENERGY_LIGHT_MSB,
ENERGY_LIGHT_MMSB,
DISTANCE,
FREQ_DISP_IRQ,
CALIB_TRCO = 0x3A,
CALIB_SRCO = 0x3B,
// RESET = 0x3C,
CALIB_RCO = 0x3D
};
// Masks for various registers, there are some redundant values that I kept
// for the sake of clarity.
enum SF_AS3935_REGSTER_MASKS {
WIPE_ALL = 0x0,
INT_MASK = 0xF,
ENERGY_MASK = 0xF,
SPI_READ_M = 0x40,
CALIB_MASK = 0x40,
OSC_MASK = 0x1F,
DISTANCE_MASK = 0x3F,
DIV_MASK = 0x3F,
NOISE_FLOOR_MASK = 0x8F,
GAIN_MASK = 0xC1,
STAT_MASK = 0xBF,
DISTURB_MASK = 0xDF,
LIGHT_MASK = 0xCF,
SPIKE_MASK = 0xF0,
THRESH_MASK = 0xF0,
CAP_MASK = 0xF0,
POWER_MASK = 0xFE
};
typedef enum INTERRUPT_STATUS {
NOISE_TO_HIGH = 0x01,
DISTURBER_DETECT = 0x04,
LIGHTNING = 0x08
} lightningStatus;
#define INDOOR 0x12
#define OUTDOOR 0xE
#define DIRECT_COMMAND 0x96
#define UNKNOWN_ERROR 0xFF
class SparkFun_AS3935
{
public:
// Constructor to be used with SPI
SparkFun_AS3935();
// Constructor to be used with I-squared-C.
SparkFun_AS3935(i2cAddress address);
// I-squared-C Begin
bool begin(TwoWire &wirePort = Wire);
// SPI begin
bool beginSPI(uint8_t user_CSPin, uint32_t spiPortSpeed, SPIClass &spiPort = SPI);
// REG0x00, bit[0], manufacturer default: 0.
// The product consumes 1-2uA while powered down. If the board is powered down
// the the TRCO will need to be recalibrated: REG0x08[5] = 1, wait 2 ms, REG0x08[5] = 0.
// SPI and I-squared-C remain active when the chip is powered down.
void powerDown();
// REG0x3A bit[7].
// This register holds the state of the timer RC oscillator (TRCO),
// after it has been calibrated. The TRCO will need to be recalibrated
// after power down. The following function wakes the IC, sends the "Direct Command" to
// CALIB_RCO register REG0x3D, waits 2ms and then checks that it has been successfully
// calibrated. Note that I-squared-C and SPI are active during power down.
bool wakeUp();
// REG0x00, bits [5:1], manufacturer default: 10010 (INDOOR).
// This funciton changes toggles the chip's settings for Indoors and Outdoors.
void setIndoorOutdoor(uint8_t _setting);
// REG0x00, bits [5:1], manufacturer default: 10010 (INDOOR).
// This function returns the indoor/outdoor settting.
uint8_t readIndoorOutdoor();
// REG0x01, bits[3:0], manufacturer default: 0010 (2).
// This setting determines the threshold for events that trigger the
// IRQ Pin.
void watchdogThreshold(uint8_t _sensitivity);
// REG0x01, bits[3:0], manufacturer default: 0010 (2).
// This function returns the threshold for events that trigger the
// IRQ Pin.
uint8_t readWatchdogThreshold();
// REG0x01, bits [6:4], manufacturer default: 010 (2).
// The noise floor level is compared to a known reference voltage. If this
// level is exceeded the chip will issue an interrupt to the IRQ pin,
// broadcasting that it can not operate properly due to noise (INT_NH).
// Check datasheet for specific noise level tolerances when setting this register.
void setNoiseLevel(uint8_t _floor);
// REG0x01, bits [6:4], manufacturer default: 010 (2).
// This function will return the set noise level threshold: default is 2.
uint8_t readNoiseLevel();
// REG0x02, bits [3:0], manufacturer default: 0010 (2).
// This setting, like the watchdog threshold, can help determine between false
// events and actual lightning. The shape of the spike is analyzed during the
// chip's signal validation routine. Increasing this value increases robustness
// at the cost of sensitivity to distant events.
void spikeRejection(uint8_t _spSensitivity);
// REG0x02, bits [3:0], manufacturer default: 0010 (2).
// This function returns the value of the spike rejection register. This value
// helps to differentiate between events and acutal lightning, by analyzing the
// shape of the spike during chip's signal validation routine.
// Increasing this value increases robustness at the cost of sensitivity to distant events.
uint8_t readSpikeRejection();
// REG0x02, bits [5:4], manufacturer default: 0 (single lightning strike).
// The number of lightning events before IRQ is set high. 15 minutes is The
// window of time before the number of detected lightning events is reset.
// The number of lightning strikes can be set to 1,5,9, or 16.
void lightningThreshold(uint8_t _strikes);
// REG0x02, bits [5:4], manufacturer default: 0 (single lightning strike).
// This function will return the number of lightning strikes must strike within
// a 15 minute window before it triggers an event on the IRQ pin. Default is 1.
uint8_t readLightningThreshold();
// REG0x02, bit [6], manufacturer default: 1.
// This register clears the number of lightning strikes that has been read in
// the last 15 minute block.
void clearStatistics(bool _clearStat);
// REG0x03, bits [3:0], manufacturer default: 0.
// When there is an event that exceeds the watchdog threshold, the register is written
// with the type of event. This consists of two messages: INT_D (disturber detected) and
// INT_L (Lightning detected). A third interrupt INT_NH (noise level too HIGH)
// indicates that the noise level has been exceeded and will persist until the
// noise has ended. Events are active HIGH. There is a one second window of time to
// read the interrupt register after lightning is detected, and 1.5 after
// disturber.
uint8_t readInterruptReg();
// REG0x03, bit [5], manufacturere default: 0.
// This setting will change whether or not disturbers trigger the IRQ Pin.
void maskDisturber(bool _state);
// REG0x03, bit [5], manufacturere default: 0.
// This setting will return whether or not disturbers trigger the IRQ Pin.
uint8_t readMaskDisturber();
// REG0x03, bit [7:6], manufacturer default: 0 (16 division ratio).
// The antenna is designed to resonate at 500kHz and so can be tuned with the
// following setting. The accuracy of the antenna must be within 3.5 percent of
// that value for proper signal validation and distance estimation.
void changeDivRatio(uint8_t _divisionRatio);
// REG0x03, bit [7:6], manufacturer default: 0 (16 division ratio).
// This function returns the current division ratio of the resonance frequency.
// The antenna resonance frequency should be within 3.5 percent of 500kHz, and
// so when modifying the resonance frequency with the internal capacitors
// (tuneCap()) it's important to keep in mind that the displayed frequency on
// the IRQ pin is divided by this number.
uint8_t readDivRatio();
// REG0x07, bit [5:0], manufacturer default: 0.
// This register holds the distance to the front of the storm and not the
// distance to a lightning strike.
uint8_t distanceToStorm();
// REG0x08, bits [5,6,7], manufacturer default: 0.
// This will send the frequency of the oscillators to the IRQ pin.
// _osc 1, bit[5] = TRCO - Timer RCO Oscillators 1.1MHz
// _osc 2, bit[6] = SRCO - System RCO at 32.768kHz
// _osc 3, bit[7] = LCO - Frequency of the Antenna
void displayOscillator(bool _state, uint8_t _osc);
// REG0x08, bits [3:0], manufacturer default: 0.
// This setting will add capacitance to the series RLC antenna on the product.
// It's possible to add 0-120pF in steps of 8pF to the antenna.
void tuneCap(uint8_t _farad);
// REG0x08, bits [3:0], manufacturer default: 0.
// This setting will return the capacitance of the internal capacitors. It will
// return a value from one to 15 multiplied by the 8pF steps of the internal
// capacitance.
uint8_t readTuneCap();
// LSB = REG0x04, bits[7:0]
// MSB = REG0x05, bits[7:0]
// MMSB = REG0x06, bits[4:0]
// This returns a 20 bit value that is the 'energy' of the lightning strike.
// According to the datasheet this is only a pure value that doesn't have any
// physical meaning.
uint32_t lightningEnergy();
// REG0x3D, bits[7:0]
// This function calibrates both internal oscillators The oscillators are tuned
// based on the resonance frequency of the antenna and so it should be trimmed
// before the calibration is done.
bool calibrateOsc();
// REG0x3C, bits[7:0]
// This function resets all settings to their default values.
void resetSettings();
private:
uint32_t _spiPortSpeed; // Given sport speed.
uint8_t _cs; // Chip select pin
uint8_t _regValue; // Variable for returned register data.
uint8_t _spiWrite; // Variable used for SPI write commands.
uint8_t _i2cWrite; // Variable used for SPI write commands.
// Address variable.
i2cAddress _address;
// This function handles all I2C write commands. It takes the register to write
// to, then will mask the part of the register that coincides with the
// setting, and then write the given bits to the register at the given
// start position.
void _writeRegister(uint8_t _reg, uint8_t _mask, uint8_t _bits, uint8_t _startPosition);
// Reads the given register.
uint8_t _readRegister(uint8_t _reg);
// I-squared-C and SPI Classes
TwoWire *_i2cPort;
SPIClass *_spiPort;
};
#endif
/******************************************************************************
SparkFunBME280.cpp
BME280 Particle Photon and Core Driver
Orginal by: Marshall Taylor @ SparkFun Electronics
Particle adaption by: Markus Haack (https://github.com/mhaack)
https://github.com/mhaack/SparkFun_BME280
Development environment specifics:
Particle IDE or Web IDE
This code is released under the [MIT License](http://opensource.org/licenses/MIT).
Please review the LICENSE.md file included with this example. If you have any questions
or concerns with licensing, please contact techsupport@sparkfun.com.
Distributed as-is; no warranty is given.
******************************************************************************/
//See SparkFunBME280.h for additional topology notes.
#include "SparkFunBME280.h"
#include "application.h"
#include <math.h>
//****************************************************************************//
//
// Settings and configuration
//
//****************************************************************************//
//Constructor -- Specifies default configuration
BME280::BME280( void )
{
//Construct with these default settings if nothing is specified
//Select interface mode
settings.commInterface = I2C_MODE; //Can be I2C_MODE, SPI_MODE
//Select address for I2C. Does nothing for SPI
settings.I2CAddress = 0x77; //Ignored for SPI_MODE
//Select CS pin for SPI. Does nothing for I2C
settings.chipSelectPin = 10;
settings.runMode = 0;
settings.tempOverSample = 0;
settings.pressOverSample = 0;
settings.humidOverSample = 0;
}
//****************************************************************************//
//
// Configuration section
//
// This uses the stored SensorSettings to start the IMU
// Use statements such as "mySensor.settings.commInterface = SPI_MODE;" to
// configure before calling .begin();
//
//****************************************************************************//
uint8_t BME280::begin()
{
//Check the settings structure values to determine how to setup the device
uint8_t dataToWrite = 0; //Temporary variable
switch (settings.commInterface)
{
case I2C_MODE:
Wire.begin();
break;
case SPI_MODE:
// start the SPI library:
SPI.begin();
// Maximum SPI frequency is 10MHz, could divide by 2 here:
SPI.setClockDivider(SPI_CLOCK_DIV32);
// Data is read and written MSb first.
SPI.setBitOrder(MSBFIRST);
// Data is captured on rising edge of clock (CPHA = 0)
// Base value of the clock is HIGH (CPOL = 1)
// This was SPI_MODE3 for RedBoard, but I had to change to
// MODE0 for Teensy 3.1 operation
SPI.setDataMode(SPI_MODE3);
// initalize the data ready and chip select pins:
pinMode(settings.chipSelectPin, OUTPUT);
digitalWrite(settings.chipSelectPin, HIGH);
break;
default:
break;
}
//Reading all compensation data, range 0x88:A1, 0xE1:E7
calibration.dig_T1 = ((uint16_t)((readRegister(BME280_DIG_T1_MSB_REG) << 8) + readRegister(BME280_DIG_T1_LSB_REG)));
calibration.dig_T2 = ((int16_t)((readRegister(BME280_DIG_T2_MSB_REG) << 8) + readRegister(BME280_DIG_T2_LSB_REG)));
calibration.dig_T3 = ((int16_t)((readRegister(BME280_DIG_T3_MSB_REG) << 8) + readRegister(BME280_DIG_T3_LSB_REG)));
calibration.dig_P1 = ((uint16_t)((readRegister(BME280_DIG_P1_MSB_REG) << 8) + readRegister(BME280_DIG_P1_LSB_REG)));
calibration.dig_P2 = ((int16_t)((readRegister(BME280_DIG_P2_MSB_REG) << 8) + readRegister(BME280_DIG_P2_LSB_REG)));
calibration.dig_P3 = ((int16_t)((readRegister(BME280_DIG_P3_MSB_REG) << 8) + readRegister(BME280_DIG_P3_LSB_REG)));
calibration.dig_P4 = ((int16_t)((readRegister(BME280_DIG_P4_MSB_REG) << 8) + readRegister(BME280_DIG_P4_LSB_REG)));
calibration.dig_P5 = ((int16_t)((readRegister(BME280_DIG_P5_MSB_REG) << 8) + readRegister(BME280_DIG_P5_LSB_REG)));
calibration.dig_P6 = ((int16_t)((readRegister(BME280_DIG_P6_MSB_REG) << 8) + readRegister(BME280_DIG_P6_LSB_REG)));
calibration.dig_P7 = ((int16_t)((readRegister(BME280_DIG_P7_MSB_REG) << 8) + readRegister(BME280_DIG_P7_LSB_REG)));
calibration.dig_P8 = ((int16_t)((readRegister(BME280_DIG_P8_MSB_REG) << 8) + readRegister(BME280_DIG_P8_LSB_REG)));
calibration.dig_P9 = ((int16_t)((readRegister(BME280_DIG_P9_MSB_REG) << 8) + readRegister(BME280_DIG_P9_LSB_REG)));
calibration.dig_H1 = ((uint8_t)(readRegister(BME280_DIG_H1_REG)));
calibration.dig_H2 = ((int16_t)((readRegister(BME280_DIG_H2_MSB_REG) << 8) + readRegister(BME280_DIG_H2_LSB_REG)));
calibration.dig_H3 = ((uint8_t)(readRegister(BME280_DIG_H3_REG)));
calibration.dig_H4 = ((int16_t)((readRegister(BME280_DIG_H4_MSB_REG) << 4) + (readRegister(BME280_DIG_H4_LSB_REG) & 0x0F)));
calibration.dig_H5 = ((int16_t)((readRegister(BME280_DIG_H5_MSB_REG) << 4) + ((readRegister(BME280_DIG_H4_LSB_REG) >> 4) & 0x0F)));
calibration.dig_H6 = ((uint8_t)readRegister(BME280_DIG_H6_REG));
//Set the oversampling control words.
//config will only be writeable in sleep mode, so first insure that.
writeRegister(BME280_CTRL_MEAS_REG, 0x00);
//Set the config word
dataToWrite = (settings.tStandby << 0x5) & 0xE0;
dataToWrite |= (settings.filter << 0x02) & 0x1C;
writeRegister(BME280_CONFIG_REG, dataToWrite);
//Set ctrl_hum first, then ctrl_meas to activate ctrl_hum
dataToWrite = settings.humidOverSample & 0x07; //all other bits can be ignored
writeRegister(BME280_CTRL_HUMIDITY_REG, dataToWrite);
//set ctrl_meas
//First, set temp oversampling
dataToWrite = (settings.tempOverSample << 0x5) & 0xE0;
//Next, pressure oversampling
dataToWrite |= (settings.pressOverSample << 0x02) & 0x1C;
//Last, set mode
dataToWrite |= (settings.runMode) & 0x03;
//Load the byte
writeRegister(BME280_CTRL_MEAS_REG, dataToWrite);
return readRegister(0xD0);
}
//Strictly resets. Run .begin() afterwards
void BME280::reset( void )
{
writeRegister(BME280_RST_REG, 0xB6);
}
//****************************************************************************//
//
// Pressure Section
//
//****************************************************************************//
float BME280::readFloatPressure( void )
{
// Returns pressure in Pa as unsigned 32 bit integer in Q24.8 format (24 integer bits and 8 fractional bits).
// Output value of “24674867” represents 24674867/256 = 96386.2 Pa = 963.862 hPa
int32_t adc_P = ((uint32_t)readRegister(BME280_PRESSURE_MSB_REG) << 12) | ((uint32_t)readRegister(BME280_PRESSURE_LSB_REG) << 4) | ((readRegister(BME280_PRESSURE_XLSB_REG) >> 4) & 0x0F);
int64_t var1, var2, p_acc;
var1 = ((int64_t)t_fine) - 128000;
var2 = var1 * var1 * (int64_t)calibration.dig_P6;
var2 = var2 + ((var1 * (int64_t)calibration.dig_P5)<<17);
var2 = var2 + (((int64_t)calibration.dig_P4)<<35);
var1 = ((var1 * var1 * (int64_t)calibration.dig_P3)>>8) + ((var1 * (int64_t)calibration.dig_P2)<<12);
var1 = (((((int64_t)1)<<47)+var1))*((int64_t)calibration.dig_P1)>>33;
if (var1 == 0)
{
return 0; // avoid exception caused by division by zero
}
p_acc = 1048576 - adc_P;
p_acc = (((p_acc<<31) - var2)*3125)/var1;
var1 = (((int64_t)calibration.dig_P9) * (p_acc>>13) * (p_acc>>13)) >> 25;
var2 = (((int64_t)calibration.dig_P8) * p_acc) >> 19;
p_acc = ((p_acc + var1 + var2) >> 8) + (((int64_t)calibration.dig_P7)<<4);
return (float)p_acc / 256.0;
}
float BME280::readFloatAltitudeMeters( void )
{
float heightOutput = 0;
heightOutput = ((float)-45846.2)*(pow(((float)readFloatPressure()/(float)101325), 0.190263) - (float)1);
return heightOutput;
}
float BME280::readFloatAltitudeFeet( void )
{
float heightOutput = 0;
heightOutput = readFloatAltitudeMeters() * 3.28084;
return heightOutput;
}
//****************************************************************************//
//
// Humidity Section
//
//****************************************************************************//
float BME280::readFloatHumidity( void )
{
// Returns humidity in %RH as unsigned 32 bit integer in Q22. 10 format (22 integer and 10 fractional bits).
// Output value of “47445” represents 47445/1024 = 46. 333 %RH
int32_t adc_H = ((uint32_t)readRegister(BME280_HUMIDITY_MSB_REG) << 8) | ((uint32_t)readRegister(BME280_HUMIDITY_LSB_REG));
int32_t var1;
var1 = (t_fine - ((int32_t)76800));
var1 = (((((adc_H << 14) - (((int32_t)calibration.dig_H4) << 20) - (((int32_t)calibration.dig_H5) * var1)) +
((int32_t)16384)) >> 15) * (((((((var1 * ((int32_t)calibration.dig_H6)) >> 10) * (((var1 * ((int32_t)calibration.dig_H3)) >> 11) + ((int32_t)32768))) >> 10) + ((int32_t)2097152)) *
((int32_t)calibration.dig_H2) + 8192) >> 14));
var1 = (var1 - (((((var1 >> 15) * (var1 >> 15)) >> 7) * ((int32_t)calibration.dig_H1)) >> 4));
var1 = (var1 < 0 ? 0 : var1);
var1 = (var1 > 419430400 ? 419430400 : var1);
return (float)(var1>>12) / 1024.0;
}
//****************************************************************************//
//
// Temperature Section
//
//****************************************************************************//
float BME280::readTempC( void )
{
// Returns temperature in DegC, resolution is 0.01 DegC. Output value of “5123” equals 51.23 DegC.
// t_fine carries fine temperature as global value
//get the reading (adc_T);
int32_t adc_T = ((uint32_t)readRegister(BME280_TEMPERATURE_MSB_REG) << 12) | ((uint32_t)readRegister(BME280_TEMPERATURE_LSB_REG) << 4) | ((readRegister(BME280_TEMPERATURE_XLSB_REG) >> 4) & 0x0F);
//By datasheet, calibrate
int64_t var1, var2;
var1 = ((((adc_T>>3) - ((int32_t)calibration.dig_T1<<1))) * ((int32_t)calibration.dig_T2)) >> 11;
var2 = (((((adc_T>>4) - ((int32_t)calibration.dig_T1)) * ((adc_T>>4) - ((int32_t)calibration.dig_T1))) >> 12) *
((int32_t)calibration.dig_T3)) >> 14;
t_fine = var1 + var2;
float output = (t_fine * 5 + 128) >> 8;
output = output / 100;
return output;
}
float BME280::readTempF( void )
{
float output = readTempC();
output = (output * 9) / 5 + 32;
return output;
}
//****************************************************************************//
//
// Utility
//
//****************************************************************************//
void BME280::readRegisterRegion(uint8_t *outputPointer , uint8_t offset, uint8_t length)
{
//define pointer that will point to the external space
uint8_t i = 0;
char c = 0;
switch (settings.commInterface)
{
case I2C_MODE:
Wire.beginTransmission(settings.I2CAddress);
Wire.write(offset);
Wire.endTransmission();
// request bytes from slave device
Wire.requestFrom(settings.I2CAddress, length);
while ( (Wire.available()) && (i < length)) // slave may send less than requested
{
c = Wire.read(); // receive a byte as character
*outputPointer = c;
outputPointer++;
i++;
}
break;
case SPI_MODE:
// take the chip select low to select the device:
digitalWrite(settings.chipSelectPin, LOW);
// send the device the register you want to read:
SPI.transfer(offset | 0x80); //Ored with "read request" bit
while ( i < length ) // slave may send less than requested
{
c = SPI.transfer(0x00); // receive a byte as character
*outputPointer = c;
outputPointer++;
i++;
}
// take the chip select high to de-select:
digitalWrite(settings.chipSelectPin, HIGH);
break;
default:
break;
}
}
uint8_t BME280::readRegister(uint8_t offset)
{
//Return value
uint8_t result;
uint8_t numBytes = 1;
switch (settings.commInterface) {
case I2C_MODE:
Wire.beginTransmission(settings.I2CAddress);
Wire.write(offset);
Wire.endTransmission();
Wire.requestFrom(settings.I2CAddress, numBytes);
while ( Wire.available() ) // slave may send less than requested
{
result = Wire.read(); // receive a byte as a proper uint8_t
}
break;
case SPI_MODE:
// take the chip select low to select the device:
digitalWrite(settings.chipSelectPin, LOW);
// send the device the register you want to read:
SPI.transfer(offset | 0x80); //Ored with "read request" bit
// send a value of 0 to read the first byte returned:
result = SPI.transfer(0x00);
// take the chip select high to de-select:
digitalWrite(settings.chipSelectPin, HIGH);
break;
default:
break;
}
return result;
}
int16_t BME280::readRegisterInt16( uint8_t offset )
{
uint8_t myBuffer[2];
readRegisterRegion(myBuffer, offset, 2); //Does memory transfer
int16_t output = (int16_t)myBuffer[0] | int16_t(myBuffer[1] << 8);
return output;
}
void BME280::writeRegister(uint8_t offset, uint8_t dataToWrite)
{
switch (settings.commInterface)
{
case I2C_MODE:
//Write the byte
Wire.beginTransmission(settings.I2CAddress);
Wire.write(offset);
Wire.write(dataToWrite);
Wire.endTransmission();
break;
case SPI_MODE:
// take the chip select low to select the device:
digitalWrite(settings.chipSelectPin, LOW);
// send the device the register you want to read:
SPI.transfer(offset & 0x7F);
// send a value of 0 to read the first byte returned:
SPI.transfer(dataToWrite);
// decrement the number of bytes left to read:
// take the chip select high to de-select:
digitalWrite(settings.chipSelectPin, HIGH);
break;
default:
break;
}
}
Comments