Cameteo/arduino/cameteo-teensy/cameteo-teensy.ino

554 lines
18 KiB
C++
Executable File

/*
* CAMETEO project
*
* This is a personnal project of weather station with
* automatic photo taking.
* This code is the weather station part and is meant
* to be run on a PJRC Teensy 3.2 board.
*
* Author : Arofarn
*
* Licence : GPL v3
*
*/
/*
* LIBRARIES
*/
//Protocols
#include <Wire.h> // library used with I2C protocol
#include <SPI.h> // SPI protocol
//Teensy3.x Real Time Clock
#include <TimeLib.h>
//SD card
#include <SD.h>
// Sensors
#include <Adafruit_Sensor.h> // Generic
#include <Adafruit_BME280.h> // BME280
#include <PWFusion_AS3935.h>
//GPS
//#include <Adafruit_GPS.h> // Adafruit Ultimate GPS
#include <TinyGPS.h> //Builtin GPS lib
/*
* DEFINE
* &
* DECLARE
*/
//Special characteres
#define DEGREE (char)176 //degree symbol in ISO 8859-1
#define DEGREE_LCD (char)222 //degree symbol for lcd display
// Pins used
#define RX1_PIN 0 // Raspberry Pi2 serial comm. - Hard wired
#define TX1_PIN 1 // Raspberry Pi2 serial comm. - Hard wired
#define RPI_PWR_PIN 2 // Raspberry Pi power control - Hard wired
#define AS3935_CS_PIN 4 // SPI SD CS - Hard wired
#define GPS_EN_PIN 6 // GPS ENable
#define RX3_PIN 7 // GPS serial comm.
#define TX3_PIN 8 // GPS serial comm.
#define AS3935_IRQ_PIN 9 // Interrupts from AS3935 lightning sensor
#define SD_CS_PIN 10 // SPI CS AS3935 lightning sensor
#define SPI_DI_PIN 11 // SPI MOSI AS3935 lightning sensor
#define SPI_DO_PIN 12 // SPI MISO AS3935 lightning sensor
#define SPI_CK_PIN 13 // SPI clock SD + AS3935 lightning sensor + built-in LED
#define BATT_VOLT_PIN 16 // Battery voltage monitoring (Analog Input)
#define LOW_BATT_PIN 17 // Low Battery signal from charger (digital input)
#define I2C_SDA_PIN 18 // I2C SDA BME280 sensor
#define I2C_SCL_PIN 19 // I2C SCL BME280 sensor
#define GPS_BUT_PIN 20 // GPS switch power control (unused)
//I2C addresses
//#define TCN75A_ADDR 0x00 //Sensor I2C bus address
//#define BMP180_ADDR 0x77
//Serial over USB communication
#define SERIAL_PORT Serial
#define SERIAL_BAUD_RATE 9600
//Raspberry Pi Serial Comm.
#define RPI_SERIAL_PORT Serial1
#define RPI_SERIAL_BAUD_RATE 9600
//GPS
#define GPS_SERIAL_PORT Serial3
#define GPS_SERIAL_BAUD_RATE 9600
//Sensors
Adafruit_BME280 bme;
float seaLevelPressure = SENSORS_PRESSURE_SEALEVELHPA;
float bme280_press;
float bme280_temp;
float bme280_alti;
float bme280_humi;
// AS3935 Lightning sensor
#define AS3935_INDOORS 0
#define AS3935_OUTDOORS 1
#define AS3935_DIST_DIS 0
#define AS3935_DIST_EN 1
#define AS3935_CAPACITANCE 72 // 72pF for THIS board (from seller)
enum strike_sources { UNKNOWN_SRC, LIGHTNING, PERTURBATION, NOISE };
volatile int8_t AS3935_ISR_Trig = 0; // Trigger for AS3935 lightning sensor
void AS3935_ISR() {
AS3935_ISR_Trig = 1;
}
PWF_AS3935 lightning(AS3935_CS_PIN, AS3935_IRQ_PIN, 33);
int as3935_src;
int as3935_distance;
long lightning_nb_total = 0;
int lightning_nb_hour = 0;
int lightning_nb_day = 0;
int as3935_perturb_total = 0;
int as3935_noise_total = 0;
int as3935_unknown_total = 0;
char lightning_log_file[12] = "lghtnng.log";
//GPS
//Adafruit_GPS GPS(&GPS_SERIAL_PORT);
TinyGPS GPS;
float gps_latitude, gps_longitude, gps_altitude; // returns +- latitude/longitude in degrees
float gps_speed, gps_course;
unsigned long gps_time, gps_date, gps_fix_age;
int gps_year;
byte gps_month, gps_day, gps_hour, gps_minutes, gps_second, gps_hundreths;
unsigned long gps_chars, gps_hdop;
unsigned short gps_sentences, gps_failed_checksum, gps_satellites;
//Miscellaneous
bool rpi_status = true;
int batt_voltage;
bool low_battery_flag = false;
// Tasks timers
elapsedMillis since_bme280;
elapsedMillis since_gps;
elapsedMillis since_display;
elapsedMillis since_serial_send;
elapsedMillis since_sd_log;
elapsedMillis since_batt_chk;
void BootMessage(String s) {
char c[13]; // char buffer for conversion String->char
s.toCharArray(c, sizeof(s));
SERIAL_PORT.printf("%-12s", c);
}
void BootOK() {
SERIAL_PORT.println("OK");
}
void BootError() {
SERIAL_PORT.println("EE");
}
void sendDataOnSerial() {
//Print date & hour on serial port
SERIAL_PORT.printf("%04d/%02d/%02d_%02d:%02d:%02d\n", year(), month(), day(), hour(), minute(), second());
//if (bme280_event.pressure) {
SERIAL_PORT.printf("Temperature:%8.2f %cC | Pressure: %8.2f hPa | Altitude:%8.2f m\n",
bme280_temp, DEGREE, bme280_press, bme280_alti);
// }
// else {
// SERIAL_PORT.printf("BMP180 Sensor error\n");
// }
SERIAL_PORT.printf("Lightning strikes (from start) : %d | Perturb.: %d | Noise : %d | Unknown detect.: %d\n", lightning_nb_total,
as3935_perturb_total,
as3935_noise_total,
as3935_unknown_total);
//SERIAL_PORT.printf("Temperature:%8.2f %cC\n", tcn75a_temp, DEGREE);
SERIAL_PORT.printf("GPS data: %04d/%02d/%02d_%02d:%02d:%02d (%d)\n", gps_year, gps_month, gps_day,
gps_hour, gps_minutes, gps_second,
gps_fix_age);
SERIAL_PORT.printf(" Latitude: %11.8f | Longitude: %11.8f | Altitude: %5.2f m\n", gps_latitude, gps_longitude, gps_altitude);
SERIAL_PORT.printf(" Speed: %4.1f km/h | Course : %4.1f %c\n", gps_speed, gps_course, DEGREE);
SERIAL_PORT.printf(" Chars: %11d | Sentences: %11d | Failed cheksum: %4d\n", gps_chars, gps_sentences, gps_failed_checksum);
SERIAL_PORT.printf("Battery : %10d mV | Low Battery : %d\n", batt_voltage, low_battery_flag);
}
bool isStartedPI() {
//Return true if the R-Pi respond to an simple request
return rpi_status;
}
void startRPI() {
//Start the Raspberry Pi via the MOSFET (grid connected to the RPI_PWR_PIN)
if (!isStartedPI()) {
pinMode(RPI_PWR_PIN, OUTPUT);
digitalWrite(RPI_PWR_PIN, LOW);
rpi_status = true;
}
}
void stopRPI() {
//Stop the Raspberry Pi via the MOSFET (grid connected to the RPI_PWR_PIN)
pinMode(RPI_PWR_PIN, INPUT); //Eteint via le MOSFET
rpi_status = false;
}
void sendDataToRPI() {
//if (isStartedPI()) {
//Print date & hour on serial port
RPI_SERIAL_PORT.printf("%04d/%02d/%02d_%02d:%02d:%02d\n", year(), month(), day(), hour(), minute(), second());
//if (bme280_event.pressure) {
RPI_SERIAL_PORT.printf("Temperature:%8.2f %cC | Pressure: %8.2f hPa | Altitude:%8.2f m\n",
bme280_temp, DEGREE, bme280_press, bme280_alti);
//}
//else {
// RPI_SERIAL_PORT.printf("BMP180 Sensor error\n");
//}
RPI_SERIAL_PORT.printf("Lightning strikes (from start) : %d | Perturb.: %d | Noise : %d | Unknown detect.: %d\n", lightning_nb_total,
as3935_perturb_total,
as3935_noise_total,
as3935_unknown_total);
RPI_SERIAL_PORT.printf("GPS data: %04d/%02d/%02d_%02d:%02d:%02d (%d)\n", gps_year, gps_month, gps_day,
gps_hour, gps_minutes, gps_second,
gps_fix_age);
RPI_SERIAL_PORT.printf(" Latitude: %11.8f | Longitude: %11.8f | Altitude: %5.2f m\n", gps_latitude, gps_longitude, gps_altitude);
RPI_SERIAL_PORT.printf(" Speed: %4.1f km/h | Course : %4.1f %c\n", gps_speed, gps_course, DEGREE);
RPI_SERIAL_PORT.printf(" Chars: %11d | Sentences: %11d | Failed cheksum: %4d\n", gps_chars, gps_sentences, gps_failed_checksum);
RPI_SERIAL_PORT.printf("Battery : %10d mV | Low Battery : %d\n", batt_voltage, low_battery_flag);
// }
// else {
// //error RPI is not started
// }
}
//SD Card
#define CONFIGFILE "config.txt"
//Configuration
char data_file[13] = "datalog.csv";
//Delays
unsigned int bme280_delay = 500;
//unsigned int dht_delay = 3000;
unsigned int gps_delay = 100;
unsigned int serial_send_delay = 5000;
unsigned int sd_log_delay = 5000;
unsigned int batt_chk_delay = 5000;
//Date and time
int TZ = 1; //Time zone
/*
* SETUP
*/
void setup() {
//To be sure Raspberry-Pi won't be turned on unexpectedly
stopRPI();
pinMode(LOW_BATT_PIN, INPUT_PULLUP);
low_battery_flag = !digitalRead(LOW_BATT_PIN);
pinMode(GPS_BUT_PIN, INPUT_PULLUP);
pinMode(GPS_EN_PIN, OUTPUT);
gps_power();
SERIAL_PORT.begin(SERIAL_BAUD_RATE);
delay(1000);
//while (!SERIAL_PORT) { }; //Wait for serial port to start
SERIAL_PORT.printf("Serial Comm. OK\nNow booting...\n");
RPI_SERIAL_PORT.begin(RPI_SERIAL_BAUD_RATE);
delay(1000);
//while (!RPI_SERIAL_PORT) { }; //Wait for serial port to start
RPI_SERIAL_PORT.printf("Raspberry's Serial Comm. OK\nNow booting...\n");
BootMessage("SDcard");
// see if the card is present and can be initialized:
if (!SD.begin(SD_CS_PIN)) {
BootError();
while(1);
}
BootOK();
BootMessage("RT Clock");
// set the Time library to use Teensy 3.0's RTC to keep time
setSyncProvider(getTeensy3Time);
if (timeStatus()!= timeSet) {
BootError();
while(1);
}
BootOK();
BootMessage("BME280 (Pressure, Humidity and Temperature)");
/* Initialise the sensor */
// if(!bme.begin())
// {
// /* There was a problem detecting the BMP085 ... check your connections */
// BootError();
// while(1);
// }
BootOK();
BootMessage("AS3935 (Lightning)");
SPI.begin();
SPI.setClockDivider(SPI_CLOCK_DIV16); // SPI speed to SPI_CLOCK_DIV16/1MHz (max 2MHz, NEVER 500kHz!)
SPI.setDataMode(SPI_MODE1); // MAX31855 is a Mode 1 device --> clock starts low, read on rising edge
SPI.setBitOrder(MSBFIRST); // data sent to chip MSb first
lightning.AS3935_DefInit(); // set registers to default
// now update sensor cal for your application and power up chip
lightning.AS3935_ManualCal(AS3935_CAPACITANCE, AS3935_OUTDOORS, AS3935_DIST_EN);
// enable interrupt (hook IRQ pin to Arduino Uno/Mega interrupt input: 0 -> pin 2, 1 -> pin 3 )
attachInterrupt(AS3935_IRQ_PIN, AS3935_ISR, RISING);
BootOK();
BootMessage("GPS");
GPS_SERIAL_PORT.begin(GPS_SERIAL_BAUD_RATE);
while (!GPS_SERIAL_PORT) {} ;
BootOK();
}
/*
* LOOP
*/
void loop() {
//Power ON/OFF GPS
gps_power();
//Lightning detection
if (AS3935_ISR_Trig != 0) {
AS3935_ISR_Trig = 0;
time_t t = now();
int distance = -9999;
int energy = -9999;
switch (as3935_src) {
case UNKNOWN_SRC:
//source inconnue
as3935_unknown_total++;
SERIAL_PORT.printf("Interruption (AS3935) : unkown source (not lightning)\n");
break;
case LIGHTNING:
//Foudre !!!
lightning_nb_total++;
distance = lightning.AS3935_GetLightningDistKm();
energy = lightning.AS3935_GetStrikeEnergyRaw();
SERIAL_PORT.printf("Interruption (AS3935) : Lightningbolt !!!\n");
SERIAL_PORT.printf("Distance : %4d km | Energy : %d \n", distance, energy);
break;
case PERTURBATION:
//Perturbation
as3935_perturb_total++;
SERIAL_PORT.printf("Interruption (AS3935) : perturbation...\n");
break;
case NOISE:
//Trop de bruit électromagnétique
as3935_noise_total++;
SERIAL_PORT.printf("Interruption (AS3935) : Too much electromagnetic noise!\n");
break;
}
writeLightningToSD(as3935_src, t, distance, energy);
}
if (since_batt_chk >= batt_chk_delay) {
since_batt_chk -= batt_chk_delay;
//Check battery status and voltage
low_battery_flag = !digitalRead(LOW_BATT_PIN);
batt_voltage = getBatteryVoltage();
}
// if (since_bme280 >= bme280_delay) {
// since_bme280 = since_bme280 - bme280_delay;
//
// bme280_press = bme.readPressure() / 100.0F;
// bme280_alti = bme.readAltitude(seaLevelPressure);
// bme280_temp = bme.readTemperature();
// bme280_humi = bme.readHumidity();
// }
if (since_gps >= gps_delay) {
since_gps = since_gps - gps_delay;
while (GPS_SERIAL_PORT.available()) {
char c = GPS_SERIAL_PORT.read();
if (GPS.encode(c)) {
GPS.get_datetime(&gps_date, &gps_time, &gps_fix_age);
GPS.crack_datetime(&gps_year, &gps_month, &gps_day,
&gps_hour, &gps_minutes, &gps_second,
&gps_hundreths, &gps_fix_age);
GPS.f_get_position(&gps_latitude, &gps_longitude, &gps_fix_age);
gps_altitude = GPS.f_altitude();
gps_speed = GPS.f_speed_kmph();
GPS.stats(&gps_chars, &gps_sentences, &gps_failed_checksum);
gps_satellites = GPS.satellites();
gps_hdop = GPS.hdop();
}
}
}
if (since_sd_log >= sd_log_delay) {
since_sd_log = since_sd_log - sd_log_delay;
writeDataToSD();
}
if (since_serial_send >= serial_send_delay) {
since_serial_send = since_serial_send - serial_send_delay;
sendDataOnSerial();
sendDataToRPI();
}
}
/*
* FUNCTIONS
*/
time_t getTeensy3Time() {
return Teensy3Clock.get();
}
void gps_power() {
if (digitalRead(GPS_BUT_PIN) == 0) { digitalWrite(GPS_EN_PIN, LOW); }
else { digitalWrite(GPS_EN_PIN, HIGH); }
}
int getBatteryVoltage() {
long mean = 0;
int U = 0;
int n = 10;
int res = 12;
analogReadResolution(res);
for (int i=0; i<n; i++) {
mean += analogRead(BATT_VOLT_PIN);
//delay(2);
}
mean /= n;
U = map(mean, 0, pow(2, res)-1, 0, 3300); // Convert data from ADC into input voltage in mV
U *= 2; //Multiple by 2 for the voltage divider
return U;
}
void writeDataToSD() {
char dir[20];
char path[60];
String directory = dayDirectory();
directory.toCharArray(dir, sizeof(directory));
sprintf(path, "%s/%s", dir, data_file);
// Test to know if the file exists before opening it, if it doesn't exist
// we will write first an header
bool no_header = SD.exists(path);
// open the file. note that only one file can be open at a time,
// so you have to close this one before opening another.
File dataFile = SD.open(path, FILE_WRITE);
// if the file is available, write to it:
if (dataFile) {
if (!no_header) {
SERIAL_PORT.printf("Creating file with CSV header : %s\n", path);
dataFile.printf("Date");
dataFile.printf(";BME280_Pressure(hPa);BME280_Temperature(degC);BME280_Humidity(%);BME280_Altitude(m)");
dataFile.printf(";TotalLightningCount;TotalPerturbationEvents;TotalNoiseDetection;TotalUnknownDetection");
dataFile.printf(";GPS_Latitude;GPS_Longitude;GPS_Altitude;GPS_Satellites;GPS_HDOP;GPS_Date");
dataFile.printf(";Battery_voltage(mV);Low_Battery_Status");
dataFile.printf("\n");
}
dataFile.printf("%04d/%02d/%02d_%02d:%02d:%02d", year(), month(), day(), hour(), minute(), second());
dataFile.printf(";%.2f;%.2f;%.0f;%.1f", bme280_press, bme280_temp, bme280_humi, bme280_alti);
dataFile.printf(";%d;%d;%d;%d", lightning_nb_total, as3935_perturb_total, as3935_noise_total, as3935_unknown_total);
dataFile.printf(";%.8f;%.8f;%.2f;%d;%d;%04d/%02d/%02d_%02d:%02d:%02d", gps_latitude, gps_longitude, gps_altitude,
gps_satellites, gps_hdop,
gps_year, gps_month, gps_day,
gps_hour, gps_minutes, gps_second);
dataFile.printf(";%d;%d", batt_voltage, low_battery_flag);
dataFile.printf("\n");
dataFile.close();
}
SERIAL_PORT.printf("Data writen : %s\n", path);
}
void writeLightningToSD(int type, time_t t, int dist, int energy) {
char dir[20];
char path[60];
String directory = dayDirectory();
directory.toCharArray(dir, sizeof(directory));
sprintf(path, "%s/%s", dir, lightning_log_file);
// Test to know if the file exists before opening it, if it doesn't exist
// we will write first an header
bool no_header = SD.exists(path);
// open the file. note that only one file can be open at a time,
// so you have to close this one before opening another.
File dataFile = SD.open(path, FILE_WRITE);
// if the file is available, write to it:
if (dataFile) {
if (!no_header) {
SERIAL_PORT.printf("Creating file with CSV header : %s\n", path);
dataFile.printf("Date");
dataFile.printf(";distance(km);energie_raw");
dataFile.printf(";TotalLightningCount;TotalPerturbationEvents;TotalNoiseDetection;TotalUnknownDetection");
dataFile.printf(";BME280_Pressure(hPa);BME280_Temperature(degC);BME280_Humidty(%);BME280_Altitude(m)");
dataFile.printf("\n");
}
dataFile.printf("%04d/%02d/%02d_%02d:%02d:%02d", year(t), month(t), day(t), hour(t), minute(t), second(t));
dataFile.printf(";%d;%d;%d", type, dist, energy);
dataFile.printf(";%d;%d;%d;%d", lightning_nb_total, as3935_perturb_total, as3935_noise_total, as3935_unknown_total);
dataFile.printf(";%.2f;%.2f;%.0f;%.1f", bme280_press, bme280_temp, bme280_humi, bme280_alti);
dataFile.printf("\n");
dataFile.close();
}
SERIAL_PORT.printf("Data writen : %s\n", path);
}
String dayDirectory() {
char dir[20] ;
sprintf(dir, "data/%04d/%02d/%02d/", year(), month(), day());
if (!SD.exists(dir))
{
SERIAL_PORT.printf("Creating directory : %s ...", dir);
SD.mkdir(dir);
SERIAL_PORT.printf(" DONE!\n");
}
return String(dir);
}