v3_firmware/src/main.cpp

355 lines
8.9 KiB
C++
Raw Normal View History

#include <Arduino.h>
#include <avr/sleep.h>
#include <avr/interrupt.h>
#include <SPI.h>
#include <Wire.h>
#include <EEPROM.h>
2021-02-01 17:29:05 +00:00
#include <lmic.h>
#include <hal/hal.h>
// Keep Track of used EEPROM Addresses
#define ADDR_SLP 0 // Sleep Interval, 2 Bytes
#include "config.h"
#include "debug.h"
2021-03-15 18:04:36 +00:00
#include "attsensor.h"
#ifdef HAS_MHZ19C
#include <MHZ19C.h>
#endif
#ifdef HAS_SG112A
#include <SG112A.h>
#endif
#ifdef HAS_SENSAIRS8
#include <SENSAIRS8.h>
#endif
#ifdef HAS_BME280
#include <BME280.h>
#endif
#ifdef HAS_SHT21
#include <SHT21.h>
#endif
2020-12-20 16:03:14 +00:00
// define the blink function and BLINK_LED Macro depending
// on the definition of LED_PIN
#ifdef LED_PIN
void blink(uint8_t num) {
pinMode(LED_PIN, OUTPUT);
digitalWrite(LED_PIN, 0);
for (uint8_t i = 0; i < num * 2; i++) {
digitalWrite(LED_PIN, !digitalRead(LED_PIN));
delay(100);
}
digitalWrite(LED_PIN, 0);
}
#define BLINK_LED(COUNT) blink(COUNT);
#else
#define BLINK_LED(COUNT)
#endif
// WS2812B RGB LEDs on the CO2 Addon Board
// Defines the Macro Function WS2812B_SETLED so we don't need #ifdefs everywhere
#ifdef WS2812B_PIN
#include <tinyNeoPixel_Static.h>
byte pixels[WS2812B_NUM * 3];
tinyNeoPixel leds = tinyNeoPixel(WS2812B_NUM, WS2812B_PIN, NEO_GRB, pixels);
#define WS2812B_SETLED(led,r,g,b) leds.setPixelColor(led,r,g,b); leds.show()
#define WS2812B_BLINK(led,r,g,b,ms) leds.setPixelColor(led,r,g,b); leds.show(); delay(ms); leds.setPixelColor(led,0,0,0); leds.show()
#else
#define WS2812B_SETLED(led,r,g,b)
#define WS2812B_BLINK(led,r,g,b,ms)
#endif
2021-03-02 16:57:20 +00:00
2021-03-15 18:04:36 +00:00
#ifndef HAS_NO_SENSORS
AttSensor* sensors[NUM_SENSORS];
2020-12-04 17:24:13 +00:00
#endif
2021-03-15 18:04:36 +00:00
int payloadBytes = 1;
2020-12-04 17:24:13 +00:00
// Define some LMIC Callbacks and Variables
void os_getArtEui (u1_t* buf) {
memcpy_P(buf, APPEUI, 8);
}
void os_getDevEui (u1_t* buf) {
memcpy_P(buf, DEVEUI, 8);
}
void os_getDevKey (u1_t* buf) {
memcpy_P(buf, APPKEY, 16);
}
static osjob_t sendjob;
2021-02-01 17:29:05 +00:00
void do_send(osjob_t* j);
// Pin-Mapping for ATTNode v3
const lmic_pinmap lmic_pins = {
.nss = PIN_PA5,
.rxtx = LMIC_UNUSED_PIN,
.rst = LMIC_UNUSED_PIN,
.dio = {PIN_PA4, PIN_PA6, LMIC_UNUSED_PIN},
};
// List of unused Pins - will be disabled for Power Saving
#if defined DEBUG || defined HAS_SG112A || defined HAS_MHZ19C || defined HAS_SENSAIRS8
const int disabledPins[] = {PIN_PB5, PIN_PB4, PIN_PB1, PIN_PB0, PIN_PC3, PIN_PC2, PIN_PC1, PIN_PC0};
#else
const int disabledPins[] = {PIN_PB5, PIN_PB4, PIN_PB3, PIN_PB2, PIN_PB1, PIN_PB0, PIN_PC3, PIN_PC2, PIN_PC1, PIN_PC0};
#endif
#ifdef BTN_PIN
volatile bool btn_pressed = 0;
volatile unsigned long btn_millis = 0;
// ISR Routine for Button
void btn_press() {
btn_pressed = 1;
btn_millis = millis();
delayMicroseconds(250000);
}
#endif
// ISR Routine for Sleep
ISR(RTC_PIT_vect)
{
/* Clear interrupt flag by writing '1' (required) */
RTC.PITINTFLAGS = RTC_PI_bm;
}
// Sleep Routine
void sleep_32s() {
cli();
while (RTC.PITSTATUS > 0) {}
RTC.PITINTCTRL = RTC_PI_bm;
// 32 Sekunden
RTC.PITCTRLA = RTC_PERIOD_CYC32768_gc | RTC_PITEN_bm;
while (RTC.PITSTATUS & RTC_CTRLBUSY_bm) {}
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
sleep_enable();
sei();
sleep_cpu();
sleep_disable();
sei();
}
// LMIC Callback Handling
void onEvent(ev_t ev) {
switch (ev) {
case EV_JOINED:
// Disable LinkCheck
LMIC_setLinkCheckMode(0);
WS2812B_BLINK(1,0,127,0,1000);
DEBUG_PRINTLN("OTAA Join Succeeded");
break;
case EV_TXCOMPLETE:
// Check for Downlink
DEBUG_PRINTLN("LoRa Packet Sent");
WS2812B_BLINK(1,0,127,0,1000);
if ((int)LMIC.dataLen == 2) {
// We got a Packet with the right size - lets assemble it into a uint16_t
DEBUG_PRINTLN("Received Downlink")
uint16_t tmpslp = (LMIC.frame[LMIC.dataBeg] << 8) | LMIC.frame[LMIC.dataBeg+1];
DEBUG_PRINT("Setting Sleep Time to: ");
DEBUG_PRINTLN(tmpslp);
sleep_time = tmpslp;
EEPROM.put(ADDR_SLP, tmpslp);
WS2812B_BLINK(1,0,0,127,250);
}
// Got to sleep for specified Time
DEBUG_PRINTLN("Going to Sleep");
for (uint16_t i = 0; i < sleep_time*2; i++) {
#ifdef BTN_PIN
if (btn_pressed) {
i = sleep_time*2;
btn_pressed = 0;
} else {
#endif
sleep_32s();
#ifdef BTN_PIN
}
#endif
}
// Schedule Next Transmit
do_send(&sendjob);
break;
}
}
// Get Battery Voltage
uint16_t readSupplyVoltage() { //returns value in millivolts to avoid floating point
uint16_t temp = 0;
analogReference(VDD);
VREF.CTRLA = VREF_ADC0REFSEL_1V5_gc;
ADC0.CTRLD = ADC_INITDLY_DLY256_gc;
ADC0_CTRLB = ADC_SAMPNUM_ACC64_gc;
uint16_t reading = analogRead(ADC_INTREF);
temp = reading / 64;
uint32_t intermediate = 1534500;
reading = 0;
reading = intermediate / temp;
return reading;
}
// Read Sensors and Send Data
void do_send(osjob_t* j) {
// Prepare LoRa Data Packet
// The struct is defined in the sensor class (or above for use without a sensor)
2021-03-15 18:04:36 +00:00
char payload[payloadBytes];
if (LMIC.opmode & OP_TXRXPEND) {
2020-12-04 17:24:13 +00:00
delay(1);
} else {
2021-03-15 18:04:36 +00:00
uint8_t curByte = 0;
// Add Battery Voltage (0.2V Accuracy stored in 1 byte)
uint32_t batv = readSupplyVoltage();
2021-03-15 18:04:36 +00:00
payload[curByte] = (uint8_t)(batv / 20);
if (batv % 20 > 9)
2021-03-15 18:04:36 +00:00
payload[curByte] += 1;
curByte++;
// Get Sensor Readings Into Data Paket
#ifndef HAS_NO_SENSOR
2021-03-15 18:04:36 +00:00
for (int i=0; i < NUM_SENSORS; i++)
curByte = sensors[i]->getSensorData(payload, curByte);
2021-03-02 16:57:20 +00:00
2021-03-02 17:32:56 +00:00
// Queue Packet for Sending
DEBUG_PRINTLN("LoRa-Packet Queued");
2021-03-15 18:04:36 +00:00
LMIC_setTxData2(1, payload, sizeof(payload), 0);
2021-03-02 17:32:56 +00:00
#if defined WS2812B_PIN && (defined HAS_SG112A || defined HAS_MHZ19C)
// CO2 PPM Levels and LED Colors
2021-03-02 17:32:56 +00:00
// < 1000 ppm green
// < 1800 ppm yellow
// > 1000 ppm red
2021-03-02 16:57:20 +00:00
if (data.ppm > 0 && data.ppm <= 1000) {
WS2812B_SETLED(0,0,127,0);
2021-03-02 16:57:20 +00:00
} else if (data.ppm > 1000 && data.ppm <= 1800) {
WS2812B_SETLED(0,127,127,0);
2021-03-02 16:57:20 +00:00
} else if (data.ppm > 1800) {
WS2812B_SETLED(0,127,0,0);
2021-03-02 16:57:20 +00:00
} else {
WS2812B_SETLED(0,0,0,0);
2021-03-02 16:57:20 +00:00
}
#endif // WS2812B
#endif // #infdef HAS_NO_SENSOR
2021-03-02 17:32:56 +00:00
}
}
void setup()
{
#ifdef DEBUG
Serial.begin(115200);
#endif
// Initialize SPI and I2C
Wire.begin();
SPI.begin();
// Disable unused Pins (for power saving)
for (int i = 0; i < (sizeof(disabledPins) / sizeof(disabledPins[0])) - 1; i++)
pinMode(disabledPins[i], INPUT_PULLUP);
#ifdef WS2812B_PIN
pinMode(WS2812B_PIN, OUTPUT);
leds.setBrightness(WS2812B_BRIGHT);
#endif
#ifdef BTN_PIN
pinMode(BTN_PIN, INPUT_PULLUP);
attachInterrupt(digitalPinToInterrupt(BTN_PIN), btn_press, FALLING);
#endif
2021-03-15 18:04:36 +00:00
// Setup all Sensors
uint8_t i = 0;
#ifdef HAS_MHZ19C
sensors[i] = new MHZ19C();
payloadBytes += sensors[i]->numBytes();
i++;
#endif
#ifdef HAS_SG112A
sensors[i] = new SG112A();
payloadBytes += sensors[i]->numBytes();
i++;
#endif
#ifdef HAS_SENSAIRS8
sensors[i] = new SENSAIRS8();
payloadBytes += sensors[i]->numBytes();
i++;
#endif
#ifdef HAS_BME280
sensors[i] = new BME280();
payloadBytes += sensors[i]->numBytes();
i++;
#endif
#ifdef HAS_SHT21
sensors[i] = new SHT21();
payloadBytes += sensors[i]->numBytes();
i++;
#endif
// Initialize all Sensors
#ifndef HAS_NO_SENSOR
for (i = 0; i < NUM_SENSORS; i++)
sensors[i]->initialize();
#endif
// Set RTC
while (RTC.STATUS > 0) {}
2020-12-04 17:24:13 +00:00
RTC.CLKSEL = RTC_CLKSEL_INT1K_gc;
while (RTC.PITSTATUS > 0) {}
// Setup LMIC
DEBUG_PRINT("Initializing LMIC...")
os_init();
LMIC_reset(); // Reset LMIC state and cancel all queued transmissions
2021-02-01 17:29:05 +00:00
LMIC_setClockError(MAX_CLOCK_ERROR * 10 / 100); // Compensate for Clock Skew
LMIC.dn2Dr = DR_SF9; // Downlink Band
LMIC_setDrTxpow(DR_SF7, 14); // Default to SF7
DEBUG_PRINTLN("Done");
// Check if Sending Interval is set in EEPROM
// if we get 65535 (0xFFFF) EEPROM was not written
uint16_t tmpsleep = 0;
EEPROM.get(ADDR_SLP, tmpsleep);
if (tmpsleep < 65535) {
DEBUG_PRINT("Setting Sleep Time from EEPROM to ");
DEBUG_PRINTLN(tmpsleep);
sleep_time = tmpsleep;
}
DEBUG_PRINTLN("Setup Finished");
2020-12-20 16:03:14 +00:00
// Schedule First Send (Triggers OTAA Join as well)
WS2812B_SETLED(1,127,127,0);
do_send(&sendjob);
}
void loop()
{
#if defined HAS_MHZ19C && defined BTN_PIN
if (digitalRead(BTN_PIN) == LOW) {
// Press Button longer than 4 Seconds -> Start MH-Z19C Calibration Routine
unsigned long loop_millis = millis();
if ((unsigned long)(loop_millis - btn_millis) >= 4000) {
WS2812B_SETLED(1,153,0,153);
pinMode(PIN_PB4, OUTPUT);
digitalWrite(PIN_PB4, LOW);
delay(7500);
digitalWrite(PIN_PB4, HIGH);
pinMode(PIN_PB4, INPUT_PULLUP);
WS2812B_SETLED(1,0,0,0);
} else {
delay(500);
}
} else {
#endif
// Only Run the LMIC loop here. Actual Sending Code is in do_send()
os_runloop_once();
#if defined HAS_MHZ19C && defined BTN_PIN
}
#endif
}