diff --git a/README.md b/README.md index e312565..270236a 100644 --- a/README.md +++ b/README.md @@ -20,16 +20,16 @@ Das sind die verwendeten Libraries [1]: | --- | ----- | ----------- | | https://github.com/mcci-catena/Adafruit_BME280_Library.git | 3dafbe1 | Wed, 13 Dec 2017 13:56:30 -0500 | | https://github.com/mcci-catena/Adafruit_Sensor.git | f2af6f4 | Tue, 1 Sep 2015 15:57:59 +0200 | -| https://github.com/mcci-catena/arduino-lmic.git | f67121c | Mon, 10 Feb 2020 10:57:04 -0500 | -| https://github.com/mcci-catena/arduino-lorawan.git | a0577e1 | Mon, 10 Feb 2020 13:21:30 -0500 | -| https://github.com/mcci-catena/Catena-Arduino-Platform.git | 85c010c | Tue, 11 Feb 2020 19:58:25 -0500 | +| https://github.com/mcci-catena/arduino-lmic.git | 6fe04ec | Tue, 12 May 2020 09:16:47 -0400 | +| https://github.com/mcci-catena/arduino-lorawan.git | 4bc0d48 | Sat, 9 May 2020 12:38:28 -0400 | +| https://github.com/mcci-catena/Catena-Arduino-Platform.git | 92019ca | Tue, 12 May 2020 01:34:08 -0400 | | https://github.com/mcci-catena/Catena-mcciadk.git | a428006 | Sat, 21 Dec 2019 20:45:26 -0500 | | https://github.com/mcci-catena/MCCI_FRAM_I2C.git | f0a5ea5 | Sat, 21 Dec 2019 16:17:01 -0500 | | https://github.com/tatobari/Q2-HX711-Arduino-Library.git | ccda8d8 | Wed, 13 Mar 2019 12:41:44 -0300 | +| https://github.com/sparkfun/SparkFun_Qwiic_Scale_NAU7802_Arduino_Library.git | 688f255 | Fri, 3 Jan 2020 12:35:22 -0700 | | https://github.com/mcci-catena/OneWire.git | d814a7b | Thu, 26 Apr 2018 03:45:27 +0800 | | https://github.com/mcci-catena/SHT1x.git | be7042c | Tue, 20 Sep 2011 13:56:23 +1000 | - `[1]: -[joerg@cinnamon libraries]$ for i in Adafruit_BME280_Library Adafruit_Sensor arduino-lmic arduino-lorawan Catena-Arduino-Platform Catena-mcciadk MCCI_FRAM_I2C Q2-HX711-Arduino-Library OneWire SHT1x ; do cd $i; echo "| $(git remote -v |grep fetch |awk '{print $2}' |tr '\n' ' ') | $(git log --pretty=format:'%h | %cD ' -n 1) |" ; cd ..; done` +[joerg@cinnamon libraries]$ for i in Adafruit_BME280_Library Adafruit_Sensor arduino-lmic arduino-lorawan Catena-Arduino-Platform Catena-mcciadk MCCI_FRAM_I2C Q2-HX711-Arduino-Library SparkFun_Qwiic_Scale_NAU7802_Arduino_Library OneWire SHT1x ; do cd $i; echo "| $(git remote -v |grep fetch |awk '{print $2}' |tr '\n' ' ') | $(git log --pretty=format:'%h | %cD ' -n 1) |" ; cd ..; done` diff --git a/helper.h b/helper.h new file mode 100644 index 0000000..d96ea98 --- /dev/null +++ b/helper.h @@ -0,0 +1,78 @@ +#ifndef _HELPER_H_ +#define _HELPER_H + +#pragma once + +#ifndef _CATENA_H_ +#include +#endif + +using namespace McciCatena; + +// the primary object +Catena gCatena; + + +//Following functions are based on "https://github.com/dndubins/QuickStats", by David Dubins + +void bubbleSort(long A[], int len) { + unsigned long newn; + unsigned long n = len; + long temp = 0; + do { + newn = 1; + for (int p = 1; p < len; p++) { + if (A[p - 1] > A[p]) { + temp = A[p]; //swap places in array + A[p] = A[p - 1]; + A[p - 1] = temp; + newn = p; + } //end if + } //end for + n = newn; + } while (n > 1); +} + +long median(long samples[], int m) //calculate the median +{ + //First bubble sort the values: https://en.wikipedia.org/wiki/Bubble_sort + long sorted[m]; // Define and initialize sorted array. + long temp = 0; // Temporary float for swapping elements + + for (int i = 0; i < m; i++) { + sorted[i] = samples[i]; + } + bubbleSort(sorted, m); // Sort the values + + if (bitRead(m, 0) == 1) { //If the last bit of a number is 1, it's odd. This is equivalent to "TRUE". Also use if m%2!=0. + return sorted[m / 2]; //If the number of data points is odd, return middle number. + } else { + return (sorted[(m / 2) - 1] + sorted[m / 2]) / 2; //If the number of data points is even, return avg of the middle two numbers. + } +} + +// Joergs STDDEV +float stddev(long samples[], int m) //calculate the stdandard deviation +{ + float sum_x; + float sum_x2; + float mean; + + float stdev; + sum_x = 0; + sum_x2 = 0; + + for (int i = 0; i < m; i++) { + sum_x = sum_x + samples[i]; + } + mean = sum_x / m; + + for (int i = 0; i < m; i++) { + sum_x2 = sum_x2 + ((samples[i] - mean) * (samples[i] - mean)); + } + + stdev = sqrt(sum_x2 / m); + + return stdev; +} +#endif diff --git a/mini-beieli-node.ino b/mini-beieli-node.ino index 166a22e..0b171f3 100644 --- a/mini-beieli-node.ino +++ b/mini-beieli-node.ino @@ -8,6 +8,9 @@ */ +// HX711: 0 => compile for hx711, 1 => compile for NAU7802 +#define HX711 1 + #include #include @@ -25,9 +28,18 @@ #include #include -#include #include "mini_beieli_node.h" +#if (HX711) +#include "mini_beieli_node_hx711.h" +#else +#include "mini_beieli_node_nau7802.h" +#endif + +#ifndef _HELPER_H_ +#include "helper.h" +#endif + using namespace McciCatena; // forwards @@ -104,10 +116,6 @@ uint32_t gRebootMs; // generic timer long t_cur; -// the primary object -Catena gCatena; - -// // the LoRaWAN backhaul. Note that we use the // Catena version so it can provide hardware-specific // information to the base class. @@ -131,9 +139,6 @@ SPIClass gSPI2( Catena_Mx25v8035f gFlash; bool fFlash; -// Scales -Q2HX711 hx711(A1, A0); - // USB power bool fUsbPower; @@ -153,13 +158,10 @@ void setup(void) { gCatena.begin(); - // Use D10 to regulate power - pinMode(D10, OUTPUT); - setup_platform(); + SetupScales(config_data.debug_level); ClearLoraData(); setup_bme280(); - //setup_scales(); setup_flash(); setup_uplink(); @@ -305,10 +307,7 @@ bool setup_scales(void) res = true; // Enable Power - digitalWrite(D10, HIGH); - - // we wait 400ms (settling time according HX711 datasheet @ 10 SPS - delay(400); + PowerupScale(); if (config_data.debug_level > 0) { gCatena.SafePrintf("%010d - setup_scale done\n", millis()); @@ -531,70 +530,6 @@ void DoDeepSleep(uint32_t sleep_time) } } -//Following functions are based on "https://github.com/dndubins/QuickStats", by David Dubins - -long median(long samples[], int m) //calculate the median -{ - //First bubble sort the values: https://en.wikipedia.org/wiki/Bubble_sort - long sorted[m]; // Define and initialize sorted array. - long temp = 0; // Temporary float for swapping elements - - for (int i = 0; i < m; i++) { - sorted[i] = samples[i]; - } - bubbleSort(sorted, m); // Sort the values - - if (bitRead(m, 0) == 1) { //If the last bit of a number is 1, it's odd. This is equivalent to "TRUE". Also use if m%2!=0. - return sorted[m / 2]; //If the number of data points is odd, return middle number. - } else { - return (sorted[(m / 2) - 1] + sorted[m / 2]) / 2; //If the number of data points is even, return avg of the middle two numbers. - } -} - -void bubbleSort(long A[], int len) { - unsigned long newn; - unsigned long n = len; - long temp = 0; - do { - newn = 1; - for (int p = 1; p < len; p++) { - if (A[p - 1] > A[p]) { - temp = A[p]; //swap places in array - A[p] = A[p - 1]; - A[p - 1] = temp; - newn = p; - } //end if - } //end for - n = newn; - } while (n > 1); -} - -long my_read_average(byte gain, byte times) { - long res; - int const num_scale_readings = 25; // number of instantaneous scale readings to calculate the median - - // we use the median, not the average, see https://community.particle.io/t/boron-gpio-provides-less-current-than-electrons-gpio/46647/13 - long readings[num_scale_readings]; // create arry to hold readings - - if (config_data.debug_level > 0) { - gCatena.SafePrintf("%010d - my_read_average, measurements: ", millis()); - } - - hx711.setGain(gain); - - for (int i = 0; i < num_scale_readings; i++) { - readings[i] = hx711.read(); // fill the array with instantaneous readings from the scale - } - - res = median(readings, num_scale_readings); // calculate median - - if (config_data.debug_level > 0) { - gCatena.SafePrintf("; median of %d samples: %d\n", num_scale_readings, res); - } - - return res; -} - void ReadSensors(SENSOR_data &sensor_data) { SENSOR_data res; int32_t weight_current32; @@ -614,11 +549,11 @@ void ReadSensors(SENSOR_data &sensor_data) { w2_0_real = config_data.cal_w2_0; if (setup_scales()) { if (config_data.debug_level > 0) { - gCatena.SafePrintf("%010d - HX711 LoadCell is ready.\n", millis()); + gCatena.SafePrintf("%010d - LoadCell is ready.\n", millis()); } gCatena.poll(); if (config_data.cal_w1_0 != NOT_ATTACHED) { - res.weight1 = (int32_t)my_read_average(32, 7); + res.weight1 = (int32_t)ReadScale('A'); if (config_data.debug_level > 0) { gCatena.SafePrintf("%010d - Load_cell 1 weight1_current: %ld\n", millis(), res.weight1); } @@ -631,7 +566,7 @@ void ReadSensors(SENSOR_data &sensor_data) { } gCatena.poll(); if (config_data.cal_w2_0 != NOT_ATTACHED) { - res.weight2 = (int32_t)my_read_average(128, 7); + res.weight2 = (int32_t)ReadScale('B'); if (config_data.debug_level > 0) { gCatena.SafePrintf("%010d - Load_cell 2 weight2_current: %ld\n", millis(), res.weight2); } @@ -645,13 +580,13 @@ void ReadSensors(SENSOR_data &sensor_data) { } else { if (config_data.debug_level > 0) { - gCatena.SafePrintf("%010d - HX711 LoadCell not ready.\n", millis()); + gCatena.SafePrintf("%010d - LoadCell not ready.\n", millis()); } } // Disable Power gCatena.poll(); - digitalWrite(D10, LOW); + PowerdownScale(); // Gewicht berechnen weight_current32 = (int32_t)((((res.weight1 - w1_0_real) / config_data.cal_w1_factor) + ((res.weight2 - w2_0_real) / config_data.cal_w2_factor)) / 5.0); @@ -1340,7 +1275,7 @@ cCommandStream::CommandStatus cmdGetScale2(cCommandStream *pThis, void *pContext cCommandStream::CommandStatus cmdCalibrateZeroScaleA(cCommandStream *pThis, void *pContext, int argc, char **argv) { setup_scales(); - config_data.cal_w1_0 = (int32_t)my_read_average(32, 10); + config_data.cal_w1_0 = (int32_t)ReadScale('A'); gCatena.getFram()->saveField(cFramStorage::kAppConf, (const uint8_t *)&config_data, sizeof(config_data)); pThis->printf("{ \"msg\": \"calibrate_zero_scale_a was successful\" }\n"); @@ -1350,7 +1285,7 @@ cCommandStream::CommandStatus cmdCalibrateZeroScaleA(cCommandStream *pThis, void cCommandStream::CommandStatus cmdCalibrateZeroScaleB(cCommandStream *pThis, void *pContext, int argc, char **argv) { setup_scales(); - config_data.cal_w2_0 = (int32_t)my_read_average(128, 10); + config_data.cal_w2_0 = (int32_t)ReadScale('B'); gCatena.getFram()->saveField(cFramStorage::kAppConf, (const uint8_t *)&config_data, sizeof(config_data)); pThis->printf("{ \"msg\": \"calibrate_zero_scale_b was successful\" }\n"); @@ -1368,7 +1303,7 @@ cCommandStream::CommandStatus cmdCalibrateScaleA(cCommandStream *pThis, void *pC config_data.cal_w1_0 = NOT_ATTACHED; } else { setup_scales(); - weight1 = my_read_average(32, 10); + weight1 = ReadScale('A'); config_data.cal_w1_factor = (float)((weight1 - config_data.cal_w1_0) / w1_gramm.toFloat()); } @@ -1390,7 +1325,7 @@ cCommandStream::CommandStatus cmdCalibrateScaleB(cCommandStream *pThis, void *pC config_data.cal_w2_0 = NOT_ATTACHED; } else { setup_scales(); - weight2 = my_read_average(128, 10); + weight2 = ReadScale('B'); config_data.cal_w2_factor = (float)((weight2 - config_data.cal_w2_0) / w2_gramm.toFloat()); } diff --git a/mini_beieli_node.h b/mini_beieli_node.h index 9405030..33eaaa5 100644 --- a/mini_beieli_node.h +++ b/mini_beieli_node.h @@ -56,7 +56,7 @@ enum { | \****************************************************************************/ -static const int32_t fwVersion = 20200229; +static const int32_t fwVersion = 20200513; static const byte INIT_PACKAGE_INTERVAL = 100; // send an init package every 100 packages; static const byte MAX_VALUES_TO_SEND = 8; diff --git a/mini_beieli_node_hx711.h b/mini_beieli_node_hx711.h new file mode 100644 index 0000000..6341b9e --- /dev/null +++ b/mini_beieli_node_hx711.h @@ -0,0 +1,89 @@ +#define SAMPLES 10 + +#include + +#ifndef _HELPER_H_ +#include "helper.h" +#endif + +// Scales +Q2HX711 hx711(A1, A0); + +byte debug_level; + +bool SetupScales(byte dbg_level) +{ + debug_level = dbg_level; + if (debug_level > 0) { + gCatena.SafePrintf("%010d - setup_scales\n", millis()); + } + + bool res; + res = true; + + // Use D10 to regulate power + pinMode(D10, OUTPUT); + + if (debug_level > 0) { + gCatena.SafePrintf("%010d - setup_scale done\n", millis()); + } + + return res; +} + +long ReadScale(char channel) +{ + if (channel == 'B') { + hx711.setGain(128); + } else { + hx711.setGain(32); + } + delay(500); + + long res; + int const num_scale_readings = 25; // number of instantaneous scale readings to calculate the median + + // we use the median, not the average, see https://community.particle.io/t/boron-gpio-provides-less-current-than-electrons-gpio/46647/13 + long readings[num_scale_readings]; // create arry to hold readings + + if (debug_level > 0) { + gCatena.SafePrintf("%010d - my_read_average, measurements:\n", millis()); + } + + for (int i = 0; i < num_scale_readings; i++) { + readings[i] = hx711.read(); // fill the array with instantaneous readings from the scale + if (debug_level > 1) { + gCatena.SafePrintf("Reading %d: %d\n", i, readings[i]); + } + } + + res = median(readings, num_scale_readings); // calculate median + + if (debug_level > 0) { + gCatena.SafePrintf("Median of %d samples: %d\n", num_scale_readings, res); + float sdev; + sdev = stddev(readings, num_scale_readings); + gCatena.SafePrintf("Standard Deviation: %d.%03d\n", (int)sdev, (int)abs(sdev * 1000) % 1000); + } + + return res; +} + +void PowerdownScale() +{ + // Disable Power + digitalWrite(D10, LOW); +} + +void PowerupScale() +{ + // Enable Power + digitalWrite(D10, HIGH); + + // we wait 400ms (settling time according HX711 datasheet @ 10 SPS + delay(400); + + if (debug_level > 0) { + gCatena.SafePrintf("%010d - setup_scale done\n", millis()); + } +} diff --git a/mini_beieli_node_nau7802.h b/mini_beieli_node_nau7802.h new file mode 100644 index 0000000..5bdf7d3 --- /dev/null +++ b/mini_beieli_node_nau7802.h @@ -0,0 +1,99 @@ +#include + +#ifndef _HELPER_H_ +#include "helper.h" +#endif + +#include "SparkFun_Qwiic_Scale_NAU7802_Arduino_Library.h" + +#define SAMPLES 10 + +NAU7802 myScale; //Create instance of the NAU7802 class + +byte debug_level; + +byte interruptPin = A0; + +bool SetupScales(byte dbg_level) +{ + debug_level = dbg_level; + + pinMode(interruptPin, INPUT); + Wire.begin(); + + if (!myScale.begin()) + { + Serial.println("Scale not detected. Please check wiring. Freezing..."); + return false; + } + Serial.println("Scale detected!"); + + myScale.setIntPolarityHigh(); + delay(500); + + return true; +} + +long ReadScale(char channel) +{ + uint8_t channelNumber; + if (channel == 'B') { + channelNumber = NAU7802_CHANNEL_1; + } else { + channelNumber = NAU7802_CHANNEL_2; + } + long startTime = millis(); + myScale.setChannel(channelNumber); + myScale.clearBit(NAU7802_PGA_PWR_PGA_CAP_EN, NAU7802_PGA_PWR); + myScale.setSampleRate(NAU7802_SPS_10); + myScale.setLDO(NAU7802_LDO_2V7); + myScale.calibrateAFE(); + //delay(500); + + long res; + int const num_scale_readings = SAMPLES; // number of instantaneous scale readings to calculate the median + + // we use the median, not the average, see https://community.particle.io/t/boron-gpio-provides-less-current-than-electrons-gpio/46647/13 + long readings[num_scale_readings]; // create arry to hold readings + + for (int i = 0; i < num_scale_readings; i++) { + while (digitalRead(interruptPin) == LOW) { + //while(! myScale.available()) { + // we set a timeout of 60 seconds for the measurement... + if ((millis() - startTime) > 60000) { + if (debug_level > 0) { + gCatena.SafePrintf("Timeout while reading scale...\n"); + } + return 0; + } + delay(1); + } + readings[i] = myScale.getReading(); // fill the array with instantaneous readings from the scale + } + + long duration = millis() - startTime; + res = median(readings, num_scale_readings); // calculate median + + if (debug_level > 0) { + gCatena.SafePrintf("Median of %d samples: %d\n", num_scale_readings, res); + float sdev; + sdev = stddev(readings, num_scale_readings); + float sdev_proc; + sdev_proc = 100 * (sdev / float(res)); + gCatena.SafePrintf("Standard Deviation: %d.%03d\n", (int)sdev, (int)abs(sdev * 1000) % 1000); + gCatena.SafePrintf("Standard Deviation / Median (Percent): %d.%03d\n", (int)sdev_proc, (int)abs(sdev_proc * 1000) % 1000); + gCatena.SafePrintf("Duration (ms): %d\n", duration); + } + + return res; +} + +void PowerdownScale() +{ + myScale.powerDown(); +} + +void PowerupScale() +{ + myScale.powerUp(); //Power up scale. This scale takes ~600ms to boot and take reading. +}