#include #include #include #include "HX711.h" #include "Adafruit_Si7021.h" #include "Adafruit_FRAM_SPI.h" // Defines #define TIMEOUTMS 60000 #define SENDDIFFTHRESHOLD 999 #define DONEPIN A5 #define VBATPIN A7 #define MAX_VALUES_TO_SEND 4 #define MAX_SHORT 32767 #define JOIN_AFTER_N_STARTUPS 100 // Enumerations enum which { NWKSKEY, APPSKEY, DEVADDR, APPEUI, DEVEUI, APPKEY }; // Constants const uint8_t FRAM_CS = 10; #if defined(ARDUINO_SAMD_ZERO) && defined(SERIAL_PORT_USBVIRTUAL) // Required for Serial on Zero based boards #define Serial SERIAL_PORT_USBVIRTUAL #endif typedef struct { band_t bands[MAX_BANDS]; u4_t channelFreq[MAX_CHANNELS]; u2_t channelDrMap[MAX_CHANNELS]; u2_t channelMap; u4_t seqnoUp; u4_t seqnoDn; u1_t datarate; u1_t dn2Dr; u4_t dn2Freq; s1_t adrTxPow; u1_t adrEnabled; } SESSION_info; // Data Types typedef struct { // for ABP u1_t nwkskey[16]; u1_t appskey[16]; u4_t devaddr; // for OTAA u1_t appeui[8]; // Swisscom: F0:3D:29:AC:71:00:00:01 u1_t deveui[8]; u1_t appkey[16]; long cal_w1_0; long cal_w2_0; float cal_w1_factor; float cal_w2_factor; byte startup_counter[MAX_VALUES_TO_SEND]; short weight[MAX_VALUES_TO_SEND]; short temperature[MAX_VALUES_TO_SEND]; byte my_position; byte logging; // 0: no, otherwise: yes byte current_startup_counter; // sesion info SESSION_info session_info; } FRAM_data; typedef struct { uint8_t version; // Versionierung des Paketformats uint8_t vbat; // Batteriespannung (1 Einheit => 20 mV) uint8_t startup_counter[MAX_VALUES_TO_SEND]; // wann die Messung gemacht wurde int16_t weight[MAX_VALUES_TO_SEND]; // Gewicht in 10-Gramm int16_t temperature[MAX_VALUES_TO_SEND]; // Temperatur in 1/10 Grad Celsius } LORA_data; // Global Variables bool lora_data_sent = false; bool usb_cable_attached = false; FRAM_data fram_data; LORA_data lora_data; Adafruit_FRAM_SPI fram = Adafruit_FRAM_SPI(FRAM_CS); // use hardware SPI //static uint8_t mydata[] = "Hello, world!"; static osjob_t sendjob; char mystring[100]; // Temp./Humidity Sensor Adafruit_Si7021 temp_sensor; // Scales HX711 scale1; HX711 scale2; // These callbacks are only used in over-the-air activation, so they are // left empty here (we cannot leave them out completely unless // DISABLE_JOIN is set in config.h, otherwise the linker will complain). void os_getArtEui (u1_t* buf) { memcpy_P(buf, fram_data.appeui, 8); } void os_getDevEui (u1_t* buf) { memcpy_P(buf, fram_data.deveui, 8); } void os_getDevKey (u1_t* buf) { memcpy_P(buf, fram_data.appkey, 16); } // Pin mapping const lmic_pinmap lmic_pins = { .nss = 8, .rxtx = LMIC_UNUSED_PIN, .rst = 4, .dio = {3, 6, LMIC_UNUSED_PIN}, }; void logit(char* logstring) { if (fram_data.logging) { Serial.print(millis()); Serial.print(": "); Serial.println(logstring); } } void print_byte_array(u1_t arr[], int n) { int i; for (i = 0; i < n; i++) { if (arr[i] < 16) Serial.write('0'); Serial.print(arr[i], HEX); } } void ShowLORAData() { Serial.println('{'); Serial.print(" \"version\": \""); Serial.print(lora_data.version); Serial.println("\","); Serial.print(" \"vbat\": \""); Serial.print(lora_data.vbat); Serial.println("\","); Serial.print(" \"startup_counter\": ["); for (int i = 0; i < MAX_VALUES_TO_SEND; i++) { Serial.print(lora_data.startup_counter[i]); if (i < (MAX_VALUES_TO_SEND - 1)) { Serial.print(","); } } Serial.println("],"); Serial.print(" \"weight\": ["); for (int i = 0; i < MAX_VALUES_TO_SEND; i++) { Serial.print(lora_data.weight[i]); if (i < (MAX_VALUES_TO_SEND - 1)) { Serial.print(","); } } Serial.println("],"); Serial.print(" \"temperature\": ["); for (int i = 0; i < MAX_VALUES_TO_SEND; i++) { Serial.print(lora_data.temperature[i]); if (i < (MAX_VALUES_TO_SEND - 1)) { Serial.print(","); } } Serial.println("],"); Serial.println("}"); } void ShowFRAMData() { Serial.println('{'); Serial.print(" \"nwkskey\": \""); print_byte_array(fram_data.nwkskey, 16); Serial.println("\", "); Serial.print(" \"appskey\": \""); print_byte_array(fram_data.appskey, 16); Serial.println("\", "); Serial.print(" \"devaddr\": \""); Serial.print(fram_data.devaddr, HEX); Serial.println("\","); Serial.print(" \"appeui\": \""); print_byte_array(fram_data.appeui, 8); Serial.println("\", "); Serial.print(" \"deveui\": \""); print_byte_array(fram_data.deveui, 8); Serial.println("\", "); Serial.print(" \"appkey\": \""); print_byte_array(fram_data.appkey, 16); Serial.println("\", "); Serial.print(" \"framecounterup\": "); Serial.print(fram_data.session_info.seqnoUp); Serial.println(","); Serial.print(" \"framecounterdown\": "); Serial.print(fram_data.session_info.seqnoDn); Serial.println(","); Serial.print(" \"cal_w1_0\": "); Serial.print(fram_data.cal_w1_0); Serial.println(","); Serial.print(" \"cal_w2_0\": "); Serial.print(fram_data.cal_w2_0); Serial.println(","); Serial.print(" \"cal_w1_factor\": "); Serial.print(fram_data.cal_w1_factor); Serial.println(","); Serial.print(" \"cal_w2_factor\": "); Serial.print(fram_data.cal_w2_factor); Serial.println(","); Serial.print(" \"startup_counter\": ["); for (int i = 0; i < MAX_VALUES_TO_SEND; i++) { Serial.print(fram_data.startup_counter[i]); if (i < (MAX_VALUES_TO_SEND - 1)) { Serial.print(","); } } Serial.println("],"); Serial.print(" \"weight\": ["); for (int i = 0; i < MAX_VALUES_TO_SEND; i++) { Serial.print(fram_data.weight[i]); if (i < (MAX_VALUES_TO_SEND - 1)) { Serial.print(","); } } Serial.println("],"); Serial.print(" \"temperature\": ["); for (int i = 0; i < MAX_VALUES_TO_SEND; i++) { Serial.print(fram_data.temperature[i]); if (i < (MAX_VALUES_TO_SEND - 1)) { Serial.print(","); } } Serial.println("],"); Serial.print(" \"my_position\": "); Serial.print(fram_data.my_position); Serial.println(","); Serial.print(" \"current_startup_counter\": "); Serial.println(fram_data.current_startup_counter); Serial.println("}"); } void onEvent (ev_t ev) { logit("Received LoraWAN Event"); switch (ev) { case EV_SCAN_TIMEOUT: logit("EV_SCAN_TIMEOUT"); break; case EV_BEACON_FOUND: logit("EV_BEACON_FOUND"); break; case EV_BEACON_MISSED: logit("EV_BEACON_MISSED"); break; case EV_BEACON_TRACKED: logit("EV_BEACON_TRACKED"); break; case EV_JOINING: logit("EV_JOINING"); break; case EV_JOINED: logit("EV_JOINED"); memcpy(fram_data.nwkskey,LMIC.nwkKey,16); memcpy(fram_data.appskey,LMIC.artKey,16); fram_data.devaddr = LMIC.devaddr; break; case EV_RFU1: logit("EV_RFU1"); break; case EV_JOIN_FAILED: logit("EV_JOIN_FAILED"); break; case EV_REJOIN_FAILED: logit("EV_REJOIN_FAILED"); break; case EV_TXCOMPLETE: logit("EV_TXCOMPLETE (includes waiting for RX windows)"); if (LMIC.txrxFlags & TXRX_ACK) logit("Received ack"); lora_data_sent = true; if (LMIC.dataLen) { sprintf(mystring,"Received %d bytes of payload", LMIC.dataLen); } break; case EV_LOST_TSYNC: logit("EV_LOST_TSYNC"); break; case EV_RESET: logit("EV_RESET"); break; case EV_RXCOMPLETE: // data received in ping slot logit("EV_RXCOMPLETE"); break; case EV_LINK_DEAD: logit("EV_LINK_DEAD"); break; case EV_LINK_ALIVE: logit("EV_LINK_ALIVE"); break; default: logit("Unknown event"); break; } } void InitFeather() { // DONEPIN must be low... pinMode(DONEPIN, OUTPUT); digitalWrite(DONEPIN, LOW); } float GetTemp() { return temp_sensor.readTemperature(); } int GetBat() { float vb = analogRead(VBATPIN); vb *= 2; // we divided by 2, so multiply back vb *= 3.3; // Multiply by 3.3V, our reference voltage return vb; } bool isUSBCableAttached() { // Reading battery; Value is in Millivolts int vbat = GetBat(); // Wir nehmen an, dass das USB-Kabel gesteckt ist, wenn die Batteriespannung // groesser als 4.4V ist (Schalter muss auf 0 sein) usb_cable_attached = (vbat >= 4400); // fuer Debugzwecke pinMode(LED_BUILTIN, OUTPUT); if (usb_cable_attached) { digitalWrite(LED_BUILTIN, HIGH); } else { digitalWrite(LED_BUILTIN, LOW); } return usb_cable_attached; } void InitSerial() { Serial.begin(115200); Serial.setTimeout(6000); //while (!Serial); delay(1000); logit("Serial Port initialized"); } bool ShouldDataBeSent() { // Data should be sent if difference is too big or if count is reached bool res = false; if (fram_data.my_position >= MAX_VALUES_TO_SEND) { res = true; } if (fram_data.my_position > 1) { if ((fram_data.weight[(fram_data.my_position - 1)] - fram_data.weight[(fram_data.my_position - 2)]) >= SENDDIFFTHRESHOLD) { res = true; } } return res; } void RestoreLoraSessionInfo() { memcpy(&LMIC.bands,&fram_data.session_info.bands,sizeof(LMIC.bands)); memcpy(&LMIC.channelFreq,&fram_data.session_info.channelFreq,sizeof(LMIC.channelFreq)); memcpy(&LMIC.channelDrMap,&fram_data.session_info.channelDrMap,sizeof(LMIC.channelDrMap)); LMIC.channelMap = fram_data.session_info.channelMap; LMIC.seqnoUp = fram_data.session_info.seqnoUp; LMIC.seqnoDn = fram_data.session_info.seqnoDn; LMIC.datarate = fram_data.session_info.datarate; LMIC.dn2Dr = fram_data.session_info.dn2Dr; LMIC.dn2Freq = fram_data.session_info.dn2Freq; LMIC.adrTxPow = fram_data.session_info.adrTxPow; LMIC.adrEnabled = fram_data.session_info.adrEnabled; } void SaveLoraSessionInfo() { memcpy(&fram_data.session_info.bands,&LMIC.bands,sizeof(LMIC.bands)); memcpy(&fram_data.session_info.channelFreq,&LMIC.channelFreq,sizeof(LMIC.channelFreq)); memcpy(&fram_data.session_info.channelDrMap,&LMIC.channelDrMap,sizeof(LMIC.channelDrMap)); fram_data.session_info.channelMap = LMIC.channelMap; fram_data.session_info.seqnoUp = LMIC.seqnoUp; fram_data.session_info.seqnoDn = LMIC.seqnoDn; fram_data.session_info.datarate = LMIC.datarate; fram_data.session_info.dn2Dr = LMIC.dn2Dr; fram_data.session_info.dn2Freq = LMIC.dn2Freq; fram_data.session_info.adrTxPow = LMIC.adrTxPow; fram_data.session_info.adrEnabled = LMIC.adrEnabled; } void InitLORA() { // LMIC init os_init(); // Reset the MAC state. Session and pending data transfers will be discarded. LMIC_reset(); // see https://startiot.telenor.com/learning/getting-started-with-adafruit-feather-m0-lora/ LMIC_setClockError(MAX_CLOCK_ERROR * 1 / 100); // We normally use ABP, but make a join for the first startup and after JOIN_AFTER_N_STARTUPS startups if (fram_data.current_startup_counter == 0) { // do nothing logit("we do nothing => OTAA"); // We use maximum SF for OTAA Joins... LMIC_setDrTxpow(DR_SF12, 14); } else if (fram_data.current_startup_counter >= JOIN_AFTER_N_STARTUPS) { logit("we do a OTAA Join again, as the startup counter is high enough..."); fram_data.current_startup_counter = 0; } else { LMIC_setSession (0x1, fram_data.devaddr, fram_data.nwkskey, fram_data.appskey); RestoreLoraSessionInfo(); } // Disable link check validation //LMIC_setLinkCheckMode(0); // TTN uses SF9 for its RX2 window. //LMIC.dn2Dr = DR_SF9; // Swisscom uses SF12 for its RX2 window. //LMIC.dn2Dr = DR_SF12; // Set data rate and transmit power for uplink (note: txpow seems to be ignored by the library) //LMIC_setDrTxpow(DR_SF7, 14); //LMIC_setDrTxpow(DR_SF12, 14); } void do_send(osjob_t* j) { // Check if there is not a current TX/RX job running if (LMIC.opmode & OP_TXRXPEND) { logit("OP_TXRXPEND, not sending"); } else { // Prepare upstream data transmission at the next possible time. //LMIC_setTxData2(1, mydata, sizeof(mydata)-1, 0); LMIC_setTxData2(1, (uint8_t*) &lora_data, sizeof(LORA_data), 0); logit("Packet queued"); } } void InitAndReaFRAM() { // we set #8 to High (CS of Lora Module), to be able to use FRAM pinMode(8, INPUT_PULLUP); digitalWrite(8, HIGH); if (fram.begin()) { logit("Found SPI FRAM"); } else { logit("No SPI FRAM found ... check your connections\r\n"); } // wir lesen die FRAM-Werte logit("Reading FRAM..."); fram.read(0x0, (uint8_t*) &fram_data, sizeof(FRAM_data)); fram_data.logging = 1; logit("FRAM was read..."); } float GetWeight() { long raw_weight1 = scale1.read_average(3); long raw_weight2 = scale2.read_average(3); return (((raw_weight1 - fram_data.cal_w1_0) / fram_data.cal_w1_factor) + ((raw_weight2 - fram_data.cal_w2_0) / fram_data.cal_w2_factor)); } void ReadSensors() { temp_sensor = Adafruit_Si7021(); // we initialize the scales logit("Initializing the scale"); scale1.begin(A3, A2); scale2.begin(A1, A0); // Now we initialize the Si7021 if (!temp_sensor.begin()) { logit("Did not find Si7021 sensor!"); } lora_data.vbat = (byte)(GetBat() / 20); // wir verwerten die Messung nur, falls der Unterschied zur Letzten registrierten Messung gross genug ist short weight = (int)GetWeight() / 10; short temperature = (int)GetTemp() * 10; short last_weight = 0; short last_temperature = 0; if (fram_data.my_position > 0) { last_weight = fram_data.weight[fram_data.my_position - 1]; last_temperature = fram_data.temperature[fram_data.my_position -1]; } Serial.println("AAA"); Serial.println(fram_data.my_position); Serial.println(temperature); Serial.println(last_temperature); Serial.println(weight); Serial.println(last_weight); Serial.println("ZZZ"); if ((fram_data.my_position == 0) || (abs(temperature - last_temperature) > 10) || (abs(weight - last_weight) > 200)) { fram_data.startup_counter[fram_data.my_position] = fram_data.current_startup_counter; fram_data.weight[fram_data.my_position] = weight; fram_data.temperature[fram_data.my_position] = temperature; fram_data.my_position++; } } void SendLoraData() { logit("Now sending packet..."); lora_data.version = 1; for (int i = 0; i < MAX_VALUES_TO_SEND; i++) { lora_data.startup_counter[i] = fram_data.startup_counter[i]; lora_data.weight[i] = fram_data.weight[i]; lora_data.temperature[i] = fram_data.temperature[i]; } // Start job //lora_data.version=11; //lora_data.vbat=22; //for (int i = 0; i < MAX_VALUES_TO_SEND; i++) { // lora_data.weight[i] = 200+i; // lora_data.temperature[i] = 300+i; //} ShowLORAData(); do_send(&sendjob); unsigned long starttime; starttime = millis(); bool logged = false; while (!lora_data_sent && ((millis() - starttime) < TIMEOUTMS)) { os_runloop_once(); if ((millis() % 1000) == 0) { if (!logged) { logit("Loop until sent..."); logged = true; } } else { logged = false; } } if (lora_data_sent) { logit("Lora Data was sent!"); } else { logit("Timeout elapsed..."); } // We clear the data... for (int i = 0; i < MAX_VALUES_TO_SEND; i++) { fram_data.startup_counter[i] = 0; fram_data.weight[i] = MAX_SHORT; fram_data.temperature[i] = MAX_SHORT; } fram_data.my_position = 0; SaveLoraSessionInfo(); } void SaveFRAM() { logit("we save the fram_data"); fram.writeEnable(true); fram.write(0x0, (uint8_t *) &fram_data, sizeof(FRAM_data) ); fram.writeEnable(false); logit("fram_data saved..."); } boolean isValidHexKey(String hk, int length) { if (hk.length() != length) { return false; } char mychar; for (int i = 0; i < hk.length(); i++) { mychar = hk.charAt(i); if (not(isHexadecimalDigit(mychar))) { return false; } } return true; } byte dehex(char c) { // Get nibble value 0...15 from character c // Treat digit if c<'A', else letter return c < 'A' ? c & 0xF : 9 + (c & 0xF); // Above assumes that c is a 'hex digit' in 0...9, A or a ... F or f. // It would make more sense to just use 16 consecutive characters, // like eg 0123456789:;<=>? or @ABCDEFGHIJKLMNO so the above // could just say `return c & 0xF;` } void WriteKey (String input, which w) { const char *hin = input.c_str(); // Get character array int clen = input.length() / 2; // Next line invalid in C++, ok in C99. Probably need to // instead declare a fixed-length array, cmd[MAXCMDLEN], etc char cmd[clen + 1]; // Leave a byte for null terminator for (int i = 0; i < 2 * clen; i += 2) { cmd[i / 2] = dehex(hin[i]) << 4 | dehex(hin[i + 1]); } cmd[clen] = 0; // Null-byte terminator if (w == APPSKEY) { memcpy(&fram_data.appskey, &cmd, 16); } else if (w == NWKSKEY) { memcpy(&fram_data.nwkskey, &cmd, 16); } else if (w == DEVADDR) { logit("write device address"); fram_data.devaddr = strtol(hin, 0, 16); } else if (w == APPEUI) { memcpy(&fram_data.appeui, &cmd, 8); } else if (w == DEVEUI) { memcpy(&fram_data.deveui, &cmd, 8); } else if (w == APPKEY) { memcpy(&fram_data.appkey, &cmd, 16); } else { logit("Invalid which"); } SaveFRAM(); } void Setup() { String s = ""; while (s != "exit") { s = Serial.readStringUntil('\n'); s.trim(); if (s == "") { //Serial.println("Leerzeile wird ignoriert..."); } else if (s == "setup") { Serial.println("{ \"msg\": \"Entering setup mode\" }"); } else if (s.startsWith("setnwkskey")) { String key; key = s.substring(11); if (isValidHexKey(key, 32)) { WriteKey(key, NWKSKEY); Serial.println("{ \"msg\": \"setnwkskey was successful\" }"); } else { Serial.println("{ \"msg\": \"Ist kein gueltiger Hex Key mit 32 Zeichen\" }"); } } else if (s.startsWith("setappskey")) { String key; key = s.substring(11); if (isValidHexKey(key, 32)) { WriteKey(key, APPSKEY); Serial.println("{ \"msg\": \"setappskey was successful\" }"); } else { Serial.println("{ \"msg\": \"Ist kein gueltiger Hex Key mit 32 Zeichen\" }"); } } else if (s.startsWith("setdevaddr")) { String key; key = s.substring(11); if (isValidHexKey(key, 8)) { WriteKey(key, DEVADDR); Serial.println("{ \"msg\": \"setdevaddr was successful\" }"); } else { Serial.println("{ \"msg\": \"Ist kein gueltiger Hex Key mit 32 Zeichen\" }"); } } else if (s.startsWith("setappeui")) { String key; key = s.substring(10); if (isValidHexKey(key, 16)) { WriteKey(key, APPEUI); Serial.println("{ \"msg\": \"setappeui was successful\" }"); } else { Serial.println("{ \"msg\": \"Ist kein gueltiger Hex Key mit 16 Zeichen\" }"); } } else if (s.startsWith("setdeveui")) { String key; key = s.substring(10); if (isValidHexKey(key, 16)) { WriteKey(key, DEVEUI); Serial.println("{ \"msg\": \"setdeveui was successful\" }"); } else { Serial.println("{ \"msg\": \"Ist kein gueltiger Hex Key mit 16 Zeichen\" }"); } } else if (s.startsWith("setappkey")) { String key; key = s.substring(10); if (isValidHexKey(key, 32)) { WriteKey(key, APPKEY); Serial.println("{ \"msg\": \"setappkey was successful\" }"); } else { Serial.println("{ \"msg\": \"Ist kein gueltiger Hex Key mit 32 Zeichen\" }"); } } else if (s == "getvalues") { Serial.println('{'); Serial.print(" \"weight\": "); Serial.print(GetWeight()); Serial.println(", "); Serial.print(" \"temperature\": "); Serial.print(GetTemp()); Serial.println(", "); Serial.print(" \"batt\": "); Serial.println(GetBat()); Serial.println("}"); } else if (s == "getrawvalues") { long raw_weight1 = scale1.read_average(3); long raw_weight2 = scale2.read_average(3); Serial.println('{'); Serial.print(" \"w1_raw\": "); Serial.print(raw_weight1); Serial.println(", "); Serial.print(" \"w2_raw\": "); Serial.print(raw_weight2); Serial.println(""); Serial.println("}"); } else if (s == "getframdata") { ShowFRAMData(); } else if (s == "calibrate_zero_1") { long raw_weight1 = scale1.read_average(3); fram_data.cal_w1_0 = (int)raw_weight1; Serial.println("{ \"msg\": \"calibrate_zero_1 was successful\" }"); } else if (s == "calibrate_zero_2") { long raw_weight2 = scale2.read_average(3); fram_data.cal_w2_0 = (int)raw_weight2; Serial.println("{ \"msg\": \"calibrate_zero_2 was successful\" }"); } else if (s.startsWith("calibrate_1")) { String w1_gramm; w1_gramm = s.substring(12); long raw_weight1 = scale1.read_average(3); //fram_data.cal_w1_factor = (w1_gramm.toFloat() / (float)raw_weight1); fram_data.cal_w1_factor = (((float)raw_weight1 - fram_data.cal_w1_0)/ w1_gramm.toFloat()); Serial.println("{ \"msg\": \"calibrate_1 was successful\" }"); } else if (s.startsWith("calibrate_2")) { String w2_gramm; w2_gramm = s.substring(12); long raw_weight2 = scale2.read_average(3); fram_data.cal_w2_factor = (((float)raw_weight2 - fram_data.cal_w2_0)/ w2_gramm.toFloat()); Serial.println("{ \"msg\": \"calibrate_2 was successful\" }"); } else if (s == "exit") { // we exit the loop } else { Serial.println("{ \"msg\": \"You sent me an unknown command (valid commands: exit,calibrate_zero_1,calibrate_zero_2,calibrate_1,calibrate_2,getvalues,getrawvalues,setnwkskey,setappskey,setdevaddr,setappeui,setdeveui,setappkey,getframdata)\" }"); } } Serial.println("We exited the setup loop..."); } void TurnOff() { logit("We turn off..."); logit("Jetzt senden wir das DONE Signal..."); delay(500); while (1) { digitalWrite(DONEPIN, HIGH); delay(5); digitalWrite(DONEPIN, LOW); delay(5); } } void setup() { fram_data.logging = 1; //fram_data.current_startup_counter = 0; InitFeather(); if (isUSBCableAttached()) { InitSerial(); } InitAndReaFRAM(); //fram_data.current_startup_counter = 0; ReadSensors(); if (isUSBCableAttached() && Serial.available()) { Setup(); } if (ShouldDataBeSent()) { InitLORA(); SendLoraData(); } fram_data.current_startup_counter++; ShowFRAMData(); SaveFRAM(); TurnOff(); } void loop() { }