├── BME280.cpp ├── BME280.h ├── BME280I2C.cpp ├── BME280I2C.h ├── LoRaWAN_TTN_Env_Node.ino ├── README.md ├── adcvcc.cpp └── adcvcc.h /BME280.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | BME280.cpp 3 | This code records data from the BME280 sensor and provides an API. 4 | This file is part of the Arduino BME280 library. 5 | Copyright (C) 2016 Tyler Glenn 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | 20 | Written: Dec 30 2015. 21 | Last Updated: Jan 1 2016. - Happy New year! 22 | 23 | This header must be included in any derived code or copies of the code. 24 | 25 | Based on the data sheet provided by Bosch for the Bme280 environmental sensor, 26 | calibration code based on algorithms providedBosch, some unit conversations courtesy 27 | of www.endmemo.com, altitude equation courtesy of NOAA, and dew point equation 28 | courtesy of Brian McNoldy at http://andrew.rsmas.miami.edu. 29 | */ 30 | 31 | 32 | /* ==== Includes ==== */ 33 | #include 34 | #include "BME280.h" 35 | /* ==== END Includes ==== */ 36 | 37 | /* ==== Methods ==== */ 38 | 39 | float BME280::CalculateTemperature(int32_t raw, int32_t& t_fine, bool celsius) 40 | { 41 | // Code based on calibration algorthim provided by Bosch. 42 | int32_t var1, var2, final; 43 | uint16_t dig_T1 = (dig[1] << 8) | dig[0]; 44 | int16_t dig_T2 = (dig[3] << 8) | dig[2]; 45 | int16_t dig_T3 = (dig[5] << 8) | dig[4]; 46 | var1 = ((((raw >> 3) - ((int32_t)dig_T1 << 1))) * ((int32_t)dig_T2)) >> 11; 47 | var2 = (((((raw >> 4) - ((int32_t)dig_T1)) * ((raw >> 4) - ((int32_t)dig_T1))) >> 12) * ((int32_t)dig_T3)) >> 14; 48 | t_fine = var1 + var2; 49 | final = (t_fine * 5 + 128) >> 8; 50 | return celsius ? final/100.0 : final/100.0*9.0/5.0 + 32.0; 51 | } 52 | 53 | float BME280::CalculateHumidity(int32_t raw, int32_t t_fine){ 54 | // Code based on calibration algorthim provided by Bosch. 55 | int32_t var1; 56 | int8_t dig_H1 = dig[24]; 57 | int16_t dig_H2 = (dig[26] << 8) | dig[25]; 58 | int8_t dig_H3 = dig[27]; 59 | int16_t dig_H4 = (dig[28] << 4) | (0x0F & dig[29]); 60 | int16_t dig_H5 = (dig[30] << 4) | ((dig[29] >> 4) & 0x0F); 61 | int8_t dig_H6 = dig[31]; 62 | 63 | var1 = (t_fine - ((int32_t)76800)); 64 | var1 = (((((raw << 14) - (((int32_t)dig_H4) << 20) - (((int32_t)dig_H5) * var1)) + 65 | ((int32_t)16384)) >> 15) * (((((((var1 * ((int32_t)dig_H6)) >> 10) * (((var1 * 66 | ((int32_t)dig_H3)) >> 11) + ((int32_t)32768))) >> 10) + ((int32_t)2097152)) * 67 | ((int32_t)dig_H2) + 8192) >> 14)); 68 | var1 = (var1 - (((((var1 >> 15) * (var1 >> 15)) >> 7) * ((int32_t)dig_H1)) >> 4)); 69 | var1 = (var1 < 0 ? 0 : var1); 70 | var1 = (var1 > 419430400 ? 419430400 : var1); 71 | return ((uint32_t)(var1 >> 12))/1024.0; 72 | } 73 | 74 | float BME280::CalculatePressure(int32_t raw, int32_t t_fine, uint8_t unit){ 75 | // Code based on calibration algorthim provided by Bosch. 76 | int64_t var1, var2, pressure; 77 | float final; 78 | 79 | uint16_t dig_P1 = (dig[7] << 8) | dig[6]; 80 | int16_t dig_P2 = (dig[9] << 8) | dig[8]; 81 | int16_t dig_P3 = (dig[11] << 8) | dig[10]; 82 | int16_t dig_P4 = (dig[13] << 8) | dig[12]; 83 | int16_t dig_P5 = (dig[15] << 8) | dig[14]; 84 | int16_t dig_P6 = (dig[17] << 8) | dig[16]; 85 | int16_t dig_P7 = (dig[19] << 8) | dig[18]; 86 | int16_t dig_P8 = (dig[21] << 8) | dig[20]; 87 | int16_t dig_P9 = (dig[23] << 8) | dig[22]; 88 | 89 | var1 = (int64_t)t_fine - 128000; 90 | var2 = var1 * var1 * (int64_t)dig_P6; 91 | var2 = var2 + ((var1 * (int64_t)dig_P5) << 17); 92 | var2 = var2 + (((int64_t)dig_P4) << 35); 93 | var1 = ((var1 * var1 * (int64_t)dig_P3) >> 8) + ((var1 * (int64_t)dig_P2) << 12); 94 | var1 = (((((int64_t)1) << 47) + var1)) * ((int64_t)dig_P1) >> 33; 95 | if (var1 == 0) { return NAN; } // Don't divide by zero. 96 | pressure = 1048576 - raw; 97 | pressure = (((pressure << 31) - var2) * 3125)/var1; 98 | var1 = (((int64_t)dig_P9) * (pressure >> 13) * (pressure >> 13)) >> 25; 99 | var2 = (((int64_t)dig_P8) * pressure) >> 19; 100 | pressure = ((pressure + var1 + var2) >> 8) + (((int64_t)dig_P7) << 4); 101 | 102 | final = ((uint32_t)pressure)/256.0; 103 | 104 | // Conversion units courtesy of www.endmemo.com. 105 | switch(unit){ 106 | case 0x1: /* hPa */ 107 | final /= 100.0; 108 | break; 109 | case 0x2: /* inHg */ 110 | final /= 3386.3752577878; /* final pa * 1inHg/3386.3752577878Pa */ 111 | break; 112 | case 0x3: /* atm */ 113 | final /= 101324.99766353; /* final pa * 1 atm/101324.99766353Pa */ 114 | break; 115 | case 0x4: /* bar */ 116 | final /= 100000.0; /* final pa * 1 bar/100kPa */ 117 | break; 118 | case 0x5: /* torr */ 119 | final /= 133.32236534674; /* final pa * 1 torr/133.32236534674Pa */ 120 | break; 121 | case 0x6: /* N/m^2 */ 122 | break; /* 1Pa / N/m^2 */ 123 | case 0x7: /* psi */ 124 | final /= 6894.744825494; /* final pa * 1psi/6894.744825494Pa */ 125 | break; 126 | default: /* Pa (case: 0) */ 127 | break; 128 | } 129 | return final; 130 | } 131 | 132 | BME280::BME280(uint8_t tosr, uint8_t hosr, uint8_t posr, uint8_t mode, uint8_t st, uint8_t filter, 133 | bool spiEnable): 134 | tempOversamplingRate(tosr), humidityOversamplingRate(hosr), 135 | pressureOversamplingRate(posr), mode(mode), standbyTime(st), filter(filter), 136 | spiEnable(spiEnable) 137 | { 138 | // ctrl_hum register. (ctrl_hum[2:0] = Humidity oversampling rate.) 139 | controlHumidity = humidityOversamplingRate; 140 | // ctrl_meas register. (ctrl_meas[7:5] = temperature oversampling rate, ctrl_meas[4:2] = pressure oversampling rate, ctrl_meas[1:0] = mode.) 141 | controlMeasure = (tempOversamplingRate << 5) | (pressureOversamplingRate << 2) | mode; 142 | // config register. (config[7:5] = standby time, config[4:2] = filter, ctrl_meas[0] = spi enable.) 143 | config = (standbyTime << 5) | (filter << 2) | spiEnable; 144 | } 145 | 146 | void BME280::setMode(uint8_t mode){ 147 | controlMeasure = controlMeasure | mode; 148 | WriteRegister(CTRL_MEAS_ADDR, controlMeasure); 149 | } 150 | 151 | float BME280::temp(bool celsius){ 152 | int32_t data[8]; 153 | int32_t t_fine; 154 | if(!ReadData(data)){ return NAN; } 155 | uint32_t rawTemp = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4); 156 | return CalculateTemperature(rawTemp, t_fine, celsius); 157 | } 158 | 159 | float BME280::pres(uint8_t unit){ 160 | int32_t data[8]; 161 | int32_t t_fine; 162 | if(!ReadData(data)){ return NAN; } 163 | uint32_t rawTemp = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4); 164 | uint32_t rawPressure = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4); 165 | CalculateTemperature(rawTemp, t_fine); 166 | return CalculatePressure(rawPressure, t_fine, unit); 167 | } 168 | 169 | float BME280::hum(){ 170 | int32_t data[8]; 171 | int32_t t_fine; 172 | if(!ReadData(data)){ return NAN; } 173 | uint32_t rawTemp = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4); 174 | uint32_t rawHumidity = (data[6] << 8) | data[7]; 175 | CalculateTemperature(rawTemp, t_fine); 176 | return CalculateHumidity(rawHumidity, t_fine); 177 | } 178 | 179 | void BME280::read(float& pressure, float& temp, float& humidity, bool metric, uint8_t p_unit){ 180 | int32_t data[8]; 181 | int32_t t_fine; 182 | if(!ReadData(data)){ 183 | pressure = temp = humidity = NAN; 184 | return; 185 | } 186 | uint32_t rawPressure = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4); 187 | uint32_t rawTemp = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4); 188 | uint32_t rawHumidity = (data[6] << 8) | data[7]; 189 | temp = CalculateTemperature(rawTemp, t_fine, metric); 190 | pressure = CalculatePressure(rawPressure, t_fine, p_unit); 191 | humidity = CalculateHumidity(rawHumidity, t_fine); 192 | } 193 | 194 | float BME280::alt(bool metric, float seaLevelPressure){ 195 | float temp, hum, pres; 196 | read(pres, temp, hum, metric); 197 | return alt(pres, metric, seaLevelPressure); 198 | } 199 | 200 | float BME280::alt(float pressure, bool metric, float seaLevelPressure){ 201 | // Equations courtesy of NOAA; 202 | float altitude = NAN; 203 | if (!isnan(pressure) && !isnan(seaLevelPressure)){ 204 | altitude = 1000.0 * ( seaLevelPressure - pressure ) / 3386.3752577878; 205 | } 206 | return metric ? altitude * 0.3048 : altitude; 207 | } 208 | 209 | float BME280::sealevel(float A) // Altitude A (in meters), temperature T in Celsius, pressure P in mb, return the equivalent pressure (in mb) at sea level. 210 | { 211 | float T(NAN), P(NAN); 212 | T = temp(true); 213 | P = pres(1); 214 | 215 | return(P / pow(1-((0.0065 *A) / (T + (0.0065 *A) + 273.15)),5.257)); 216 | } 217 | 218 | float BME280::dew(bool metric){ 219 | float temp, hum, pres; 220 | read(pres, temp, hum, metric); 221 | return dew(temp, hum, metric); 222 | } 223 | 224 | float BME280::dew(float temp, float hum, bool metric){ 225 | // Equations courtesy of Brian McNoldy from http://andrew.rsmas.miami.edu; 226 | float dewPoint = NAN; 227 | if (metric && !isnan(temp) && !isnan(hum)){ 228 | dewPoint = 243.04 * (log(hum/100.0) + ((17.625 * temp)/(243.04 + temp))) 229 | /(17.625 - log(hum/100.0) - ((17.625 * temp)/(243.04 + temp))); 230 | }else if (!isnan(temp) && !isnan(hum)){ 231 | float ctemp = (temp - 32.0) * 5.0/9.0; 232 | dewPoint = 243.04 * (log(hum/100.0) + ((17.625 * ctemp)/(243.04 + ctemp))) 233 | /(17.625 - log(hum/100.0) - ((17.625 * ctemp)/(243.04 + ctemp))); 234 | dewPoint = dewPoint * 9.0/5.0 + 32.0; 235 | } 236 | return dewPoint; 237 | } 238 | 239 | /* ==== END Methods ==== */ 240 | -------------------------------------------------------------------------------- /BME280.h: -------------------------------------------------------------------------------- 1 | /* 2 | BME280.h 3 | This code records data from the BME280 sensor and provides an API. 4 | This file is part of the Arduino BME280 library. 5 | Copyright (C) 2016 Tyler Glenn 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | 20 | Written: Dec 30 2015. 21 | Last Updated: Jan 1 2016. - Happy New year! 22 | This code is licensed under the GNU LGPL and is open for ditrbution 23 | and copying in accordance with the license. 24 | This header must be included in any derived code or copies of the code. 25 | 26 | Based on the data sheet provided by Bosch for the Bme280 environmental sensor. 27 | */ 28 | #ifndef TG_BME_280_H 29 | #define TG_BME_280_H 30 | 31 | /* ==== Includes ==== */ 32 | #include "Arduino.h" 33 | /* ==== END Includes ==== */ 34 | 35 | /* ==== Defines ==== */ 36 | #define CTRL_HUM_ADDR 0xF2 37 | #define CTRL_MEAS_ADDR 0xF4 38 | #define CONFIG_ADDR 0xF5 39 | #define PRESS_ADDR 0xF7 40 | #define TEMP_ADDR 0xFA 41 | #define HUM_ADDR 0xFD 42 | #define TEMP_DIG_ADDR 0x88 43 | #define PRESS_DIG_ADDR 0x8E 44 | #define HUM_DIG_ADDR1 0xA1 45 | #define HUM_DIG_ADDR2 0xE1 46 | #define ID_ADDR 0xD0 47 | #define BME_ID 0x60 48 | #define BMP_ID 0x58 49 | /* ==== END Defines ==== */ 50 | 51 | 52 | class BME280{ 53 | protected: 54 | uint8_t tempOversamplingRate, humidityOversamplingRate, pressureOversamplingRate; // B000 = Skipped, B001 = x1, B010 = x2, B011 = x4, B100 = x8, B101/other = x16 55 | uint8_t mode; // Sleep = B00, Normal = B01 and B10, Forced = B11 56 | uint8_t standbyTime; // B000 = 0.5ms, B001 = 62.5ms, B010 = 125ms, B011 = 250ms, B100 = 250ms, B101 = 1000ms, B110 = 10ms, B111 = 20ms 57 | uint8_t filter; // B000 = off, B001 = 2, B010 = 4, B011 = 8, B100/other = 16 58 | uint8_t controlHumidity; // ctrl_hum register. (ctrl_hum[2:0] = Humidity oversampling rate.) 59 | uint8_t controlMeasure; // ctrl_meas register. (ctrl_meas[7:5] = temperature oversampling rate, ctrl_meas[4:2] = pressure oversampling rate, ctrl_meas[1:0] = mode.) 60 | uint8_t config; // config register. (config[7:5] = standby time, config[4:2] = filter, ctrl_meas[0] = spi enable.) 61 | uint8_t dig[32]; 62 | bool spiEnable; 63 | 64 | 65 | /* ==== Write values to BME280 registers. ==== */ 66 | virtual void WriteRegister(uint8_t addr, uint8_t data)=0; 67 | 68 | /* ==== Read the the trim data from the BME280, return true if successful. ==== */ 69 | virtual bool ReadTrim()=0; 70 | 71 | /* ==== Read the raw data from the BME280 into an array and return true if successful. ==== */ 72 | virtual bool ReadData(int32_t data[8])=0; 73 | 74 | /* ==== Calculate the temperature from the BME280 raw data and BME280 trim, return a float. ==== */ 75 | float CalculateTemperature(int32_t raw, int32_t& t_fine, bool celsius = true); 76 | 77 | /* ==== Calculate the humidity from the BME280 raw data and BME280 trim, return a float. ==== */ 78 | float CalculateHumidity(int32_t raw, int32_t t_fine); 79 | 80 | /* ==== Calculate the pressure from the BME280 raw data and BME280 trim, return a float. ==== */ 81 | float CalculatePressure(int32_t raw, int32_t t_fine, uint8_t unit = 0x0); // unit: B000/other = Pa, B001 = hPa, B010 = inHg, B011 = atm, B100 = bar, B101 = torr, B110 = N/m^2, B111 = psi 82 | 83 | public: 84 | /* ==== Constructor used to create the class. All parameters have default values. ==== */ 85 | BME280(uint8_t tosr = 0x1, uint8_t hosr = 0x1, uint8_t posr = 0x1, uint8_t mode = 0x1, 86 | uint8_t st = 0x5, uint8_t filter = 0x0, bool spiEnable = false); // Oversampling = 1, mode = forced, standby time = 1000ms, filter = none. 87 | 88 | /* ==== Method used at start up to initialize the class. ==== */ 89 | virtual bool begin()=0; 90 | 91 | void setMode(uint8_t mode); 92 | 93 | /* ==== Read the temperature from the BME280 and return a float. ==== */ 94 | float temp(bool celsius = true); 95 | 96 | /* ==== Read the pressure from the BME280 and return a float with the specified unit. ==== */ 97 | float pres(uint8_t unit = 0x0); // unit: B000 = Pa, B001 = hPa, B010 = Hg, B011 = atm, B100 = bar, B101 = torr, B110 = N/m^2, B111 = psi 98 | 99 | /* ==== Read the humidity from the BME280 and return a percentage as a float. ==== */ 100 | float hum(); 101 | 102 | /* ==== Read the data from the BME280 with the specified units. ==== */ 103 | void read(float& pressure, float& temp, float& humidity, bool metric = true, uint8_t p_unit = 0x0); 104 | 105 | 106 | /* ==== Read the data from the BME280 with the specified units and then calculate the altitude. ==== */ 107 | float alt(bool metric = true, float seaLevelPressure = 101325); 108 | 109 | /* ==== Calculate the altitude based on the pressure with the specified units. ==== */ 110 | float alt(float pressure, bool metric = true, float seaLevelPressure = 101325); // Pressure given in Pa. 111 | 112 | /* convert current pressure to sea-level pressure, returns sealevel pressure in mbar*/ 113 | float sealevel(float A); // A: current altitude (meters). 114 | 115 | /* ==== Read BME280 data and calculate the dew point with the specified units. ==== */ 116 | float dew(bool metric = true); 117 | 118 | /* ==== Calculate the dew point based on the temperature and humidity with the specified units. ==== */ 119 | float dew(float temp, float hum, bool metric = true); 120 | 121 | }; 122 | #endif // TG_BME_280_H 123 | -------------------------------------------------------------------------------- /BME280I2C.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | BME280I2CI2C.cpp 3 | This code records data from the BME280I2C sensor and provides an API. 4 | This file is part of the Arduino BME280I2C library. 5 | Copyright (C) 2016 Tyler Glenn 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | 20 | Written: Dec 30 2015. 21 | Last Updated: Jan 1 2016. - Happy New year! 22 | 23 | This header must be included in any derived code or copies of the code. 24 | 25 | Based on the data sheet provided by Bosch for the BME280I2C environmental sensor, 26 | calibration code based on algorithms providedBosch, some unit conversations courtesy 27 | of www.endmemo.com, altitude equation courtesy of NOAA, and dew point equation 28 | courtesy of Brian McNoldy at http://andrew.rsmas.miami.edu. 29 | */ 30 | 31 | 32 | /* ==== Includes ==== */ 33 | #include 34 | #include "BME280I2C.h" 35 | /* ==== END Includes ==== */ 36 | 37 | /* ==== Methods ==== */ 38 | 39 | bool BME280I2C::Initialize() { 40 | WriteRegister(CTRL_HUM_ADDR, controlHumidity); 41 | WriteRegister(CTRL_MEAS_ADDR, controlMeasure); 42 | WriteRegister(CONFIG_ADDR, config); 43 | return ReadTrim(); 44 | } 45 | 46 | 47 | void BME280I2C::WriteRegister(uint8_t addr, uint8_t data) 48 | { 49 | Wire.beginTransmission(bme_280_addr); 50 | Wire.write(addr); 51 | Wire.write(data); 52 | Wire.endTransmission(); 53 | } 54 | 55 | bool BME280I2C::ReadTrim() 56 | { 57 | uint8_t ord(0); 58 | 59 | // Temp. Dig 60 | Wire.beginTransmission(bme_280_addr); 61 | Wire.write(TEMP_DIG_ADDR); 62 | Wire.endTransmission(); 63 | 64 | Wire.requestFrom(bme_280_addr, (uint8_t)6); 65 | while(Wire.available()){ 66 | dig[ord++] = Wire.read(); 67 | } 68 | 69 | // Pressure Dig 70 | Wire.beginTransmission(bme_280_addr); 71 | Wire.write(PRESS_DIG_ADDR); 72 | Wire.endTransmission(); 73 | 74 | Wire.requestFrom(bme_280_addr, (uint8_t)18); 75 | while(Wire.available()){ 76 | dig[ord++] = Wire.read(); 77 | } 78 | 79 | // Humidity Dig 1 80 | Wire.beginTransmission(bme_280_addr); 81 | Wire.write(HUM_DIG_ADDR1); 82 | Wire.endTransmission(); 83 | 84 | Wire.requestFrom(bme_280_addr, (uint8_t)1); 85 | while(Wire.available()){ 86 | dig[ord++] = Wire.read(); 87 | } 88 | 89 | // Humidity Dig 2 90 | Wire.beginTransmission(bme_280_addr); 91 | Wire.write(HUM_DIG_ADDR2); 92 | Wire.endTransmission(); 93 | 94 | Wire.requestFrom(bme_280_addr, (uint8_t)7); 95 | while(Wire.available()){ 96 | dig[ord++] = Wire.read(); 97 | } 98 | 99 | #ifdef DEBUG_ON 100 | Serial.print("Dig: "); 101 | for(int i = 0; i < 32; ++i) 102 | { 103 | Serial.print(dig[i], HEX); 104 | Serial.print(" "); 105 | } 106 | Serial.println(); 107 | #endif 108 | 109 | return ord == 32; 110 | } 111 | 112 | bool BME280I2C::ReadData(int32_t data[8]){ 113 | uint8_t ord = 0; 114 | 115 | // for forced mode we need to write the mode to BME280 register before reading 116 | if ( (mode == 0x01) || (mode == 0x10) ) 117 | setMode(mode); 118 | 119 | // Registers are in order. So we can start at the pressure register and read 8 bytes. 120 | Wire.beginTransmission(bme_280_addr); 121 | Wire.write(PRESS_ADDR); 122 | Wire.endTransmission(); 123 | 124 | Wire.requestFrom(bme_280_addr, (uint8_t)8); 125 | while(Wire.available()){ 126 | data[ord++] = Wire.read(); 127 | } 128 | 129 | #ifdef DEBUG_ON 130 | Serial.print("Data: "); 131 | for(int i = 0; i < 8; ++i) 132 | { 133 | Serial.print(data[i], HEX); 134 | Serial.print(" "); 135 | } 136 | Serial.println(); 137 | #endif 138 | 139 | return ord == 8; 140 | } 141 | 142 | 143 | BME280I2C::BME280I2C(uint8_t tosr, uint8_t hosr, uint8_t posr, uint8_t mode, uint8_t st, uint8_t filter, 144 | bool spiEnable, uint8_t bme_280_addr): 145 | BME280(tosr, hosr, posr, mode, st, filter, spiEnable), bme_280_addr(bme_280_addr) 146 | { 147 | // ctrl_hum register. (ctrl_hum[2:0] = Humidity oversampling rate.) 148 | controlHumidity = humidityOversamplingRate; 149 | // ctrl_meas register. (ctrl_meas[7:5] = temperature oversampling rate, ctrl_meas[4:2] = pressure oversampling rate, ctrl_meas[1:0] = mode.) 150 | controlMeasure = (tempOversamplingRate << 5) | (pressureOversamplingRate << 2) | mode; 151 | // config register. (config[7:5] = standby time, config[4:2] = filter, ctrl_meas[0] = spi enable.) 152 | config = (standbyTime << 5) | (filter << 2) | spiEnable; 153 | } 154 | 155 | #if defined(ARDUINO_ARCH_ESP8266) 156 | bool BME280I2C::begin(int SDA, int SCL) { 157 | // allow config of pins 158 | Wire.begin(SDA,SCL); 159 | return Initialize(); 160 | } 161 | #endif 162 | 163 | bool BME280I2C::begin(){ 164 | Wire.begin(); 165 | return Initialize(); 166 | } 167 | 168 | /* ==== END Methods ==== */ 169 | -------------------------------------------------------------------------------- /BME280I2C.h: -------------------------------------------------------------------------------- 1 | /* 2 | BME280I2C.h 3 | This code records data from the BME280 sensor and provides an API. 4 | This file is part of the Arduino BME280 library. 5 | Copyright (C) 2016 Tyler Glenn 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 3 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | 20 | Written: Sep 19 2016. 21 | Last Updated: Sep 19 2016. - Happy Fall! <5 22 | This code is licensed under the GNU LGPL and is open for ditrbution 23 | and copying in accordance with the license. 24 | This header must be included in any derived code or copies of the code. 25 | 26 | Based on the data sheet provided by Bosch for the Bme280 environmental sensor. 27 | */ 28 | #ifndef TG_BME_280_I2C_H 29 | #define TG_BME_280_I2C_H 30 | 31 | /* ==== Includes ==== */ 32 | #include "BME280.h" 33 | /* ==== END Includes ==== */ 34 | 35 | /* ==== Defines ==== */ 36 | 37 | /* ==== END Defines ==== */ 38 | 39 | 40 | class BME280I2C: public BME280{ 41 | uint8_t bme_280_addr; 42 | 43 | /* ==== Write configuration to BME280, return true if successful. ==== */ 44 | bool Initialize(); 45 | 46 | /* ==== Write values to BME280 registers. ==== */ 47 | virtual void WriteRegister(uint8_t addr, uint8_t data); 48 | 49 | /* ==== Read the the trim data from the BME280, return true if successful. ==== */ 50 | virtual bool ReadTrim(); 51 | 52 | /* ==== Read the raw data from the BME280 into an array and return true if successful. ==== */ 53 | virtual bool ReadData(int32_t data[8]); 54 | 55 | public: 56 | /* ==== Constructor used to create the class. All parameters have default values. ==== */ 57 | BME280I2C(uint8_t tosr = 0x1, uint8_t hosr = 0x1, uint8_t posr = 0x1, uint8_t mode = 0x1, 58 | uint8_t st = 0x5, uint8_t filter = 0x0, bool spiEnable = false, 59 | uint8_t bme_280_addr = 0x76); // Oversampling = 1, mode = forced, standby time = 1000ms, filter = none. 60 | 61 | /* ==== Method used at start up to initialize the class. Starts the I2C interface. ==== */ 62 | virtual bool begin(); 63 | #if defined(ARDUINO_ARCH_ESP8266) 64 | /* ==== On esp8266 it is possible to define I2C pins ==== */ 65 | bool begin(int SDA, int SCL); 66 | #endif 67 | 68 | }; 69 | #endif // TG_BME_280_I2C_H 70 | -------------------------------------------------------------------------------- /LoRaWAN_TTN_Env_Node.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * ------------------------------------------------------------------------ 3 | * "PH2LB LICENSE" (Revision 1) : (based on "THE BEER-WARE LICENSE" Rev 42) 4 | * wrote this file. As long as you retain this notice 5 | * you can do modify it as you please. It's Free for non commercial usage 6 | * and education and if we meet some day, and you think this stuff is 7 | * worth it, you can buy me a beer in return 8 | * Lex Bolkesteijn 9 | * ------------------------------------------------------------------------ 10 | * Filename : LoRaWAN_TTN_Env_Node.ino 11 | * Version : 1.1 (BETA) 12 | * ------------------------------------------------------------------------ 13 | * Description : A low power BME280 based datalogger for the ThingsNetwork. 14 | * with deepsleep support and variable interval 15 | * ------------------------------------------------------------------------ 16 | * Revision : 17 | * - 2018-jan-04 1.2 reset status detection (on brownout detect skip Tx on startup) 18 | * - 2017-dec-05 1.1 minor code updates 19 | * - 2017-jul-17 1.0 first "beta" 20 | * ------------------------------------------------------------------------ 21 | * Hardware used : 22 | * - Arduino Pro-Mini 3.3V 23 | * - RFM95W 24 | * - BME280 sensor. 25 | * ------------------------------------------------------------------------ 26 | * Software used : 27 | * - LMIC https://github.com/matthijskooijman/arduino-lmic 28 | * - LowPower library https://github.com/rocketscream/Low-Power 29 | * - MiniCore loader https://forum.arduino.cc/index.php?topic=412070 https://github.com/MCUdude/MiniCore 30 | * To use a brownout detection of 1.8V. 31 | * - special adcvcc library from Charles (see : https://www.thethingsnetwork.org/forum/t/full-arduino-mini-lorawan-and-1-3ua-sleep-mode/8059/32?u=lex_ph2lb ) 32 | * - BME280 library https://github.com/finitespace/BME280 33 | * 34 | * For licenses of the used libraries, check the links above. 35 | * ------------------------------------------------------------------------ 36 | * Aditional note : 37 | * 38 | * I use a HTTP integration on the TTN with the decoder below. 39 | * ------------------------------------------------------------------------ 40 | * TODO LIST : 41 | * - add more sourcode comment 42 | * 43 | * 44 | **************************************************************************************** 45 | * TheThingsNetwork Payload functions : 46 | function Decoder(bytes, port) 47 | { 48 | var retValue = { 49 | bytes: bytes 50 | }; 51 | 52 | retValue.batt = bytes[0] / 10.0; 53 | if (retValue.batt === 0) 54 | delete retValue.batt; 55 | 56 | if (bytes.length >= 2) 57 | { 58 | retValue.humidity = bytes[1]; 59 | if (retValue.humidity === 0) 60 | delete retValue.humidity; 61 | } 62 | if (bytes.length >= 3) 63 | { 64 | retValue.temperature = (((bytes[2] << 8) | bytes[3]) / 10.0) - 40.0; 65 | } 66 | if (bytes.length >= 5) 67 | { 68 | retValue.pressure = ((bytes[4] << 8) | bytes[5]); 69 | if (retValue.pressure === 0) 70 | delete retValue.pressure; 71 | } 72 | 73 | return retValue; 74 | } 75 | */ 76 | 77 | 78 | /* 79 | ***************************************************************************************** 80 | * INCLUDE FILES 81 | ***************************************************************************************** 82 | */ 83 | #include 84 | #include 85 | #include 86 | #include 87 | #include 88 | #include "adcvcc.h" 89 | #include "BME280I2C.h" 90 | 91 | /* 92 | * Defines 93 | */ 94 | 95 | #define debugSerial Serial 96 | #define SHOW_DEBUGINFO 97 | #define debugPrintLn(...) { if (debugSerial) debugSerial.println(__VA_ARGS__); } 98 | #define debugPrint(...) { if (debugSerial) debugSerial.print(__VA_ARGS__); } 99 | #define debugFlush() { if (debugSerial) debugSerial.flush(); } 100 | 101 | // When enable BROWNOUTDISABLED be sure that you have BOD fused. 102 | // #define BROWNOUTDISABLED 103 | 104 | #define NORMALINTERVAL 900 // 15 minutes (normal) 105 | //#define NORMALINTERVAL 32 // 32 second (test highspeed) 106 | 107 | // Restrict to channel0 if uncommented; otherwise all channels (allways use SF7) 108 | // #define CHANNEL0 109 | 110 | //// Choice on of the LMIC pinnings below. 111 | 112 | //// Pin mapping for TTN Enschede board 113 | //#define LMIC_NSS 10 114 | //#define LMIC_RXTX 6 115 | //#define LMIC_RST LMIC_UNUSED_PIN 116 | //#define LMIC_DIO0 4 117 | //#define LMIC_DIO1 5 118 | //#define LMIC_DIO2 7 119 | 120 | // Pin mapping CH2I (check out : https://www.thethingsnetwork.org/forum/t/full-arduino-mini-lorawan-and-1-3ua-sleep-mode/8059 ) 121 | #define LMIC_NSS 10 122 | #define LMIC_RXTX LMIC_UNUSED_PIN 123 | #define LMIC_RST LMIC_UNUSED_PIN 124 | #define LMIC_DIO0 2 125 | #define LMIC_DIO1 7 126 | #define LMIC_DIO2 8 127 | 128 | 129 | const lmic_pinmap lmic_pins = { 130 | .nss = LMIC_NSS, 131 | .rxtx = LMIC_RXTX, 132 | .rst = LMIC_RST, 133 | .dio = {LMIC_DIO0, LMIC_DIO1, LMIC_DIO2}, 134 | }; 135 | 136 | // UPDATE WITH YOURE TTN KEYS AND ADDR 137 | static const PROGMEM u1_t NWKSKEY[16] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; // LoRaWAN NwkSKey, network session key 138 | static const u1_t PROGMEM APPSKEY[16] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00A6, 0x00 }; // LoRaWAN AppSKey, application session key 139 | static const u4_t DEVADDR = 0x00000000 ; // LoRaWAN end-device address (DevAddr) 140 | 141 | 142 | // These callbacks are only used in over-the-air activation, so they are 143 | // left empty here (we cannot leave them out completely unless 144 | // DISABLE_JOIN is set in config.h, otherwise the linker will complain). 145 | void os_getArtEui (u1_t* buf) { } 146 | void os_getDevEui (u1_t* buf) { } 147 | void os_getDevKey (u1_t* buf) { } 148 | 149 | static osjob_t sendjob; 150 | 151 | // global enviromental parameters 152 | static float temp = 0.0; 153 | static float pressure = 0.0; 154 | static float humidity = 0.0; 155 | 156 | int interval = NORMALINTERVAL; 157 | 158 | byte LMIC_transmitted = 0; 159 | int LMIC_event_Timeout = 0; 160 | 161 | // Schedule TX every this many seconds (might become longer due to duty 162 | // cycle limitations). 163 | // const unsigned TX_INTERVAL = 60; 164 | 165 | BME280I2C bme; // Default : forced mode, standby time = 1000 ms 166 | // Oversampling = pressure ×1, temperature ×1, humidity ×1, filter off, 167 | 168 | /* ====================================================================== 169 | Function: ADC_vect 170 | Purpose : IRQ Handler for ADC 171 | Input : - 172 | Output : - 173 | Comments: used for measuring 8 samples low power mode, ADC is then in 174 | free running mode for 8 samples 175 | ====================================================================== */ 176 | ISR(ADC_vect) 177 | { 178 | // Increment ADC counter 179 | _adc_irq_cnt++; 180 | } 181 | 182 | 183 | void updateEnvParameters() 184 | { 185 | delay(1000); 186 | temp = bme.temp(true); 187 | pressure = bme.pres(1); // 1 = hPa (milliBar) 188 | humidity = bme.hum(); 189 | } 190 | 191 | void onEvent (ev_t ev) 192 | { 193 | debugPrint(os_getTime()); 194 | debugPrint(": "); 195 | debugPrintLn(ev); 196 | switch(ev) 197 | { 198 | case EV_SCAN_TIMEOUT: 199 | //debugPrintLn(F("EV_SCAN_TIMEOUT")); 200 | break; 201 | case EV_BEACON_FOUND: 202 | //debugPrintLn(F("EV_BEACON_FOUND")); 203 | break; 204 | case EV_BEACON_MISSED: 205 | //debugPrintLn(F("EV_BEACON_MISSED")); 206 | break; 207 | case EV_BEACON_TRACKED: 208 | //debugPrintLn(F("EV_BEACON_TRACKED")); 209 | break; 210 | case EV_JOINING: 211 | //debugPrintLn(F("EV_JOINING")); 212 | break; 213 | case EV_JOINED: 214 | //debugPrintLn(F("EV_JOINED")); 215 | break; 216 | case EV_RFU1: 217 | //debugPrintLn(F("EV_RFU1")); 218 | break; 219 | case EV_JOIN_FAILED: 220 | //debugPrintLn(F("EV_JOIN_FAILED")); 221 | break; 222 | case EV_REJOIN_FAILED: 223 | //debugPrintLn(F("EV_REJOIN_FAILED")); 224 | break; 225 | case EV_TXCOMPLETE: 226 | debugPrintLn(F("EV_TXC")); 227 | if (LMIC.txrxFlags & TXRX_ACK) 228 | debugPrintLn(F("R ACK")); // Received ack 229 | if (LMIC.dataLen) 230 | { 231 | debugPrintLn(F("R ")); 232 | debugPrintLn(LMIC.dataLen); 233 | debugPrintLn(F(" bytes")); // of payload 234 | } 235 | // Schedule next transmission 236 | // os_setTimedCallback(&sendjob, os_getTime()+sec2osticks(TX_INTERVAL), do_send); 237 | LMIC_transmitted = 1; 238 | break; 239 | case EV_LOST_TSYNC: 240 | //debugPrintLn(F("EV_LOST_TSYNC")); 241 | break; 242 | case EV_RESET: 243 | //debugPrintLn(F("EV_RESET")); 244 | break; 245 | case EV_RXCOMPLETE: 246 | // data received in ping slot 247 | //debugPrintLn(F("EV_RXCOMPLETE")); 248 | break; 249 | case EV_LINK_DEAD: 250 | //debugPrintLn(F("EV_LINK_DEAD")); 251 | break; 252 | case EV_LINK_ALIVE: 253 | //debugPrintLn(F("EV_LINK_ALIVE")); 254 | break; 255 | default: 256 | //debugPrintLn(F("Unknown event")); 257 | break; 258 | } 259 | } 260 | 261 | void do_send(osjob_t* j) 262 | { 263 | // Check if there is not a current TX/RX job running 264 | if (LMIC.opmode & OP_TXRXPEND) 265 | { 266 | debugPrintLn(F("OP_TXRXPEND")); //P_TXRXPEND, not sending 267 | } 268 | else 269 | { 270 | // Prepare upstream data transmission at the next possible time. 271 | 272 | // Here the sensor information should be retrieved 273 | // Pressure: 300...1100 hPa 274 | // Temperature: -40…85°C 275 | updateEnvParameters(); 276 | updateEnvParameters(); 277 | 278 | int batt = (int)(readVcc() / 100); // readVCC returns mVolt need just 100mVolt steps 279 | byte batvalue = (byte)batt; // no problem putting it into a int. 280 | 281 | #ifdef SHOW_DEBUGINFO 282 | debugPrint(F("T=")); 283 | debugPrintLn(temp); 284 | 285 | debugPrint(F("P=")); 286 | debugPrintLn(pressure); 287 | 288 | debugPrint(F("H=")); 289 | debugPrintLn(humidity); 290 | 291 | debugPrint(F("B=")); 292 | debugPrintLn(batt); 293 | debugPrint(F("BV=")); 294 | debugPrintLn(batvalue); 295 | #endif 296 | int t = (int)((temp + 40.0) * 10.0); 297 | // t = t + 40; => t [-40..+85] => [0..125] => t = t * 10; => t [0..125] => [0..1250] 298 | int p = (int)(pressure); // p [300..1100] 299 | int h = (int)(humidity); 300 | 301 | unsigned char mydata[6]; 302 | mydata[0] = batvalue; 303 | mydata[1] = h & 0xFF; 304 | mydata[2] = t >> 8; 305 | mydata[3] = t & 0xFF; 306 | mydata[4] = p >> 8; 307 | mydata[5] = p & 0xFF; 308 | 309 | LMIC_setTxData2(1, mydata, sizeof(mydata), 0); 310 | debugPrintLn(F("PQ")); //Packet queued 311 | } 312 | // Next TX is scheduled after TX_COMPLETE event. 313 | } 314 | 315 | 316 | bool bootFromBrownOut = false; 317 | 318 | // shows the bootstatus on serial output and set bootFromBrownout flag. 319 | void showBootStatus(uint8_t _mcusr) 320 | { 321 | debugPrint(F("mcusr = ")); 322 | debugPrint(_mcusr, HEX); 323 | debugPrint(F(" > ")); 324 | if (_mcusr & (1<= 2) 40 | { 41 | retValue.humidity = bytes[1]; 42 | if (retValue.humidity === 0) 43 | delete retValue.humidity; 44 | } 45 | if (bytes.length >= 3) 46 | { 47 | retValue.temperature = (((bytes[2] << 8) | bytes[3]) / 10.0) - 40.0; 48 | } 49 | if (bytes.length >= 5) 50 | { 51 | retValue.pressure = ((bytes[4] << 8) | bytes[5]); 52 | if (retValue.pressure === 0) 53 | delete retValue.pressure; 54 | } 55 | 56 | return retValue; 57 | } 58 | -------------------------------------------------------------------------------- /adcvcc.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "Arduino.h" 5 | 6 | 7 | volatile uint8_t _adc_irq_cnt; 8 | 9 | 10 | // add this to the INO of you project. 11 | ///* ====================================================================== 12 | //Function: ADC_vect 13 | //Purpose : IRQ Handler for ADC 14 | //Input : - 15 | //Output : - 16 | //Comments: used for measuring 8 samples low power mode, ADC is then in 17 | // free running mode for 8 samples 18 | //====================================================================== */ 19 | //ISR(ADC_vect) 20 | //{ 21 | // // Increment ADC counter 22 | // _adc_irq_cnt++; 23 | //} 24 | 25 | 26 | /* ====================================================================== 27 | Function: readADCLowNoise 28 | Purpose : Read Analog Value with reducing noise 29 | Input : true return the average value, false return only the sum 30 | Output : average value read 31 | Comments: hard coded to read 8 samples each time 32 | AD MUX Channel and ADC must have been set before this call 33 | ====================================================================== */ 34 | uint16_t readADCLowNoise(boolean average) 35 | { 36 | uint8_t low, high; 37 | uint16_t sum = 0; 38 | 39 | // Start 1st Conversion, but ignore it, can be hazardous 40 | ADCSRA |= _BV(ADSC); 41 | 42 | // wait for first dummy conversion 43 | while (bit_is_set(ADCSRA,ADSC)); 44 | 45 | // Init our measure counter 46 | _adc_irq_cnt = 0; 47 | 48 | // We want to have a interrupt when the conversion is done 49 | ADCSRA |= _BV( ADIE ); 50 | 51 | // Loop thru samples 52 | // 8 samples (we don't take the 1st one) 53 | do { 54 | // Enable Noise Reduction Sleep Mode 55 | set_sleep_mode( SLEEP_MODE_ADC ); 56 | sleep_enable(); 57 | 58 | // Wait until conversion is finished 59 | do { 60 | // enabled IRQ before sleeping 61 | sei(); 62 | sleep_cpu(); 63 | cli(); 64 | } 65 | // Check is done with interrupts disabled to avoid a race condition 66 | while (bit_is_set(ADCSRA,ADSC)); 67 | 68 | // No more sleeping 69 | sleep_disable(); 70 | sei(); 71 | 72 | // read low first 73 | low = ADCL; 74 | high = ADCH; 75 | 76 | // Sum the total 77 | sum += ((high << 8) | low); 78 | } 79 | while (_adc_irq_cnt<8); 80 | 81 | // No more interrupts needed for this 82 | // we finished the job 83 | ADCSRA &= ~ _BV( ADIE ); 84 | 85 | // Return the average divided by 8 (8 samples) 86 | return ( average ? sum >> 3 : sum ); 87 | } 88 | 89 | /* ====================================================================== 90 | Function: readVcc 91 | Purpose : Read and Calculate V powered, the Voltage on Arduino VCC pin 92 | Input : - 93 | Output : value readed 94 | Comments: ADC Channel input is modified 95 | ====================================================================== */ 96 | uint16_t readVcc() 97 | { 98 | uint16_t value, _vcc; 99 | 100 | // Enable ADC (just in case going out of low power) 101 | power_adc_enable(); 102 | ADCSRA |= _BV(ADEN) ; 103 | 104 | // Read 1.1V reference against AVcc 105 | // REFS1 REFS0 --> 0 1, AVcc internal ref. -Selects AVcc external reference 106 | // MUX3 MUX2 MUX1 MUX0 --> 1110 1.1V (VBG) -Selects channel 14, bandgap voltage, to measure 107 | ADMUX = (0< 5500 ) _vcc = 5500 ; 124 | 125 | // Vcc in millivolts 126 | return ( _vcc ); 127 | } 128 | -------------------------------------------------------------------------------- /adcvcc.h: -------------------------------------------------------------------------------- 1 | 2 | extern uint16_t readVcc(); 3 | 4 | extern volatile uint8_t _adc_irq_cnt; 5 | --------------------------------------------------------------------------------