diff --git a/include/GeigerData.h b/include/GeigerData.h index ddd1e80..77da777 100644 --- a/include/GeigerData.h +++ b/include/GeigerData.h @@ -8,7 +8,8 @@ const float STS6_CPM_PER_USPH = 875; // Holds pulse counter history and performs calculations -class GeigerData { +class GeigerData +{ public: const uint16_t sampleCount; const uint16_t sampleSeconds; @@ -21,9 +22,8 @@ private: uint16_t prev(uint16_t index); public: - GeigerData(uint16_t sampleCount, uint16_t sampleSeconds, - float cpm_per_uSph); + float cpm_per_uSph); virtual ~GeigerData(); virtual void addPulses(uint16_t pulses); diff --git a/src/GeigerData.cpp b/src/GeigerData.cpp index 39e0002..b72249e 100644 --- a/src/GeigerData.cpp +++ b/src/GeigerData.cpp @@ -1,45 +1,53 @@ #include "GeigerData.h" GeigerData::GeigerData(uint16_t sampleCount, uint16_t sampleSeconds, - float cpm_per_uSph) : - sampleCount(sampleCount), sampleSeconds(sampleSeconds), cpm_per_uSph( - cpm_per_uSph), pulsesPerSample(new uint16_t[sampleCount]) { + float cpm_per_uSph) : sampleCount(sampleCount), sampleSeconds(sampleSeconds), cpm_per_uSph(cpm_per_uSph), pulsesPerSample(new uint16_t[sampleCount]) +{ currentSample = 0; - for (int i = 0; i < sampleCount; i++) { + for (int i = 0; i < sampleCount; i++) + { pulsesPerSample[i] = 0; } } -GeigerData::~GeigerData() { +GeigerData::~GeigerData() +{ delete[] pulsesPerSample; } -uint16_t GeigerData::next(uint16_t index) { +uint16_t GeigerData::next(uint16_t index) +{ return index + 1 < sampleCount ? index + 1 : 0; } -uint16_t GeigerData::prev(uint16_t index) { +uint16_t GeigerData::prev(uint16_t index) +{ return index > 0 ? index - 1 : sampleCount - 1; } -void GeigerData::addPulses(uint16_t pulses) { +void GeigerData::addPulses(uint16_t pulses) +{ if (pulsesPerSample[currentSample] <= UINT16_MAX - pulses) pulsesPerSample[currentSample] += pulses; } -void GeigerData::nextSample() { +void GeigerData::nextSample() +{ currentSample = next(currentSample); pulsesPerSample[currentSample] = 0; } -uint16_t GeigerData::getCurrentSample() { +uint16_t GeigerData::getCurrentSample() +{ return currentSample; } -uint32_t GeigerData::getPreviousPulses(uint16_t offset, uint16_t samples) { +uint32_t GeigerData::getPreviousPulses(uint16_t offset, uint16_t samples) +{ uint32_t pulses = 0; uint16_t index = (currentSample + sampleCount - offset) % sampleCount; - for (uint16_t i = 0; i < samples; i++) { + for (uint16_t i = 0; i < samples; i++) + { pulses += pulsesPerSample[index]; index = prev(index); } @@ -47,7 +55,8 @@ uint32_t GeigerData::getPreviousPulses(uint16_t offset, uint16_t samples) { return pulses; } -float GeigerData::toMicroSievertPerHour(uint32_t pulses, uint16_t samples) { +float GeigerData::toMicroSievertPerHour(uint32_t pulses, uint16_t samples) +{ float cpm = pulses / (sampleSeconds / 60. * samples); return cpm / cpm_per_uSph; } diff --git a/src/display.cpp b/src/display.cpp index eb91564..22040cd 100644 --- a/src/display.cpp +++ b/src/display.cpp @@ -6,13 +6,15 @@ // on I2C GPIOs SCL 22 and SDA 21 U8G2_SH1106_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, 22, 21); -void initDisplay() { +void initDisplay() +{ // high I2c clock still results in about 100ms buffer transmission to OLED: u8g2.setBusClock(1000000); u8g2.begin(); } -void renderDigits(char uSphStr[16], char cpmStr[16]) { +void renderDigits(char uSphStr[16], char cpmStr[16]) +{ uint16_t y = 14; uint16_t xCpm = 56; uint16_t xUSph = 127; @@ -37,19 +39,20 @@ void renderDigits(char uSphStr[16], char cpmStr[16]) { u8g2.print("cnt/min"); } -void renderHistoryBarGraph(GeigerData &geigerData) { +void renderHistoryBarGraph(GeigerData &geigerData) +{ const uint16_t bars = 120; const uint16_t maxBarHeight = 40; const uint16_t samplesPerBar = geigerData.sampleCount / bars; - const uint16_t barsPerMinute = 60 - / (samplesPerBar * geigerData.sampleSeconds); + const uint16_t barsPerMinute = 60 / (samplesPerBar * geigerData.sampleSeconds); // determine max value for y scale: uint16_t offset = geigerData.getCurrentSample() % samplesPerBar + 1; uint32_t maxPulses = 0; - for (int16_t i = 0; i < bars - 1; i++) { + for (int16_t i = 0; i < bars - 1; i++) + { const uint32_t prevPulses = geigerData.getPreviousPulses(offset, - samplesPerBar); + samplesPerBar); if (prevPulses > maxPulses) maxPulses = prevPulses; @@ -57,13 +60,13 @@ void renderHistoryBarGraph(GeigerData &geigerData) { } const float maxUSph = geigerData.toMicroSievertPerHour(maxPulses, - samplesPerBar); - const float uSphPerPixel = maxUSph > 40. ? 10. : maxUSph > 4. ? 1. : - maxUSph > 0.4 ? 0.1 : 0.01; + samplesPerBar); + const float uSphPerPixel = maxUSph > 40. ? 10. : maxUSph > 4. ? 1. : maxUSph > 0.4 ? 0.1 : 0.01; // labels and grid u8g2.setFont(u8g2_font_4x6_tn); char s[10]; - for (uint16_t i = 10; i <= maxBarHeight; i += 10) { + for (uint16_t i = 10; i <= maxBarHeight; i += 10) + { u8g2.setCursor(0, 63 - i + 3); if (uSphPerPixel >= 0.1) sprintf(s, "%.0f", i * uSphPerPixel); @@ -71,20 +74,22 @@ void renderHistoryBarGraph(GeigerData &geigerData) { sprintf(s, ".%.0f", i * uSphPerPixel * 10); u8g2.print(s); - for (int16_t x = 127 - barsPerMinute; x >= 8; x -= barsPerMinute) { + for (int16_t x = 127 - barsPerMinute; x >= 8; x -= barsPerMinute) + { u8g2.drawPixel(x, 63 - i); } } // bars offset = geigerData.getCurrentSample() % samplesPerBar + 1; - for (int16_t i = 0; i < bars - 1; i++) { + for (int16_t i = 0; i < bars - 1; i++) + { const uint32_t prevPulses = geigerData.getPreviousPulses(offset, - samplesPerBar); + samplesPerBar); const float uSph = geigerData.toMicroSievertPerHour(prevPulses, - samplesPerBar); + samplesPerBar); offset += samplesPerBar; - uint16_t barHeight = 1 + (int) ((uSph / uSphPerPixel)); + uint16_t barHeight = 1 + (int)((uSph / uSphPerPixel)); if (barHeight > 40) barHeight = 40; @@ -92,7 +97,8 @@ void renderHistoryBarGraph(GeigerData &geigerData) { } } -void updateDisplay(GeigerData &geigerData, char uSphStr[16], char cpmStr[16]) { +void updateDisplay(GeigerData &geigerData, char uSphStr[16], char cpmStr[16]) +{ u8g2.clearBuffer(); renderDigits(uSphStr, cpmStr); renderHistoryBarGraph(geigerData); diff --git a/src/ingest.cpp b/src/ingest.cpp index c7253a9..8ead05c 100644 --- a/src/ingest.cpp +++ b/src/ingest.cpp @@ -6,21 +6,27 @@ const char *thingsPeakUrl = "api.thingspeak.com"; -bool connect() { +bool connect() +{ uint16_t retries = 3; - while (WiFi.status() != WL_CONNECTED && (--retries) > 0) { + while (WiFi.status() != WL_CONNECTED && (--retries) > 0) + { Serial.print("Trying to connect to "); Serial.print(wifiSsid); Serial.print(" ... "); WiFi.begin(wifiSsid, wifiPassword); uint16_t waitRemaining = 8; - while (WiFi.status() != WL_CONNECTED && (--waitRemaining) > 0) { + while (WiFi.status() != WL_CONNECTED && (--waitRemaining) > 0) + { delay(500); } - if (WiFi.status() == WL_CONNECTED) { + if (WiFi.status() == WL_CONNECTED) + { Serial.println("successful"); return true; - } else { + } + else + { Serial.print("failed status="); Serial.println(WiFi.status()); } @@ -29,39 +35,43 @@ bool connect() { return WiFi.status() == WL_CONNECTED; } -void initIngest() { +void initIngest() +{ connect(); } -void deinitIngest() { - if (WiFi.status() == WL_CONNECTED) { +void deinitIngest() +{ + if (WiFi.status() == WL_CONNECTED) + { Serial.println("Disconnecting WiFi"); WiFi.disconnect(true, true); } } -void ingest(GeigerData &geigerData, uint16_t intervalSamples) { +void ingest(GeigerData &geigerData, uint16_t intervalSamples) +{ if (!connect()) return; WiFiClient client; - if (!client.connect(thingsPeakUrl, 80)) { + if (!client.connect(thingsPeakUrl, 80)) + { Serial.print("Connecting to "); Serial.print(thingsPeakUrl); Serial.println(" failed"); - } else { + } + else + { const uint32_t pulses = geigerData.getPreviousPulses(1, - intervalSamples); + intervalSamples); const uint32_t cpm = uint32_t( - pulses - / ((float) intervalSamples * geigerData.sampleSeconds - / 60.) + 0.5); + pulses / ((float)intervalSamples * geigerData.sampleSeconds / 60.) + 0.5); const float uSph = geigerData.toMicroSievertPerHour(pulses, - intervalSamples); + intervalSamples); - const String content = "api_key=" + String(thingspeakApiKey) - + "&field1=" + String(cpm) + "&field2=" + String(uSph, 3); + const String content = "api_key=" + String(thingspeakApiKey) + "&field1=" + String(cpm) + "&field2=" + String(uSph, 3); Serial.print("Ingesting cpm="); Serial.print(cpm); @@ -85,15 +95,18 @@ void ingest(GeigerData &geigerData, uint16_t intervalSamples) { client.print(content); uint16_t timeout = 40; - while (client.available() == 0 && (--timeout) > 0) { + while (client.available() == 0 && (--timeout) > 0) + { delay(50); } - if (client.available() == 0) { + if (client.available() == 0) + { Serial.println("failed (no response)"); } Serial.println("response:"); - while (client.available()) { + while (client.available()) + { char c = client.read(); Serial.write(c); } diff --git a/src/main.cpp b/src/main.cpp index 59b6acf..2328b43 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,24 +6,6 @@ #include "ingest.h" #include "GeigerData.h" -void setup() ; -void pulse() ; -uint32_t calcRemainingWait() ; -boolean wifiSwitchOn() ; -uint16_t takeSampleNoSleep() ; -uint16_t takeSampleLowPower() ; -void loop() ; - -#include "Arduino.h" - -#include "driver/pcnt.h" -#include "driver/gpio.h" -#include "driver/rtc_io.h" - -#include "display.h" -#include "ingest.h" -#include "GeigerData.h" - // ~400µs high pulses from Geiger tube on GPIO 18 #define PULSE_PIN 18 #define PULSE_GPIO GPIO_NUM_18 @@ -51,7 +33,14 @@ uint32_t sampleStart = 0; const int16_t ingestInterval = 60; int16_t ingestCountdown; -void setup() { +void pulse(); +uint32_t calcRemainingWait(); +boolean wifiSwitchOn(); +uint16_t takeSampleNoSleep(); +uint16_t takeSampleLowPower(); + +void setup() +{ Serial.begin(921600); Serial.println("Starting!"); @@ -67,7 +56,8 @@ void setup() { // WiFi switch input pinMode(WIFI_SWITCH_PIN, INPUT_PULLUP); - if (wifiSwitchOn()) { + if (wifiSwitchOn()) + { initIngest(); } @@ -76,97 +66,8 @@ void setup() { ingestCountdown = ingestInterval; } -// interrupt handler -void pulse() { - ++intPulseCount; -} - -uint32_t calcRemainingWait() { - const uint32_t remaining = sampleMicros - (micros() - sampleStart); - return remaining > sampleMicros ? 0 : remaining; -} - -boolean wifiSwitchOn() { - return digitalRead(WIFI_SWITCH_PIN) == 0; -} - -uint16_t takeSampleNoSleep() { - attachInterrupt(PULSE_PIN, pulse, RISING); - - int32_t remainingWait = calcRemainingWait(); - delayMicroseconds(remainingWait); - sampleStart = micros(); - noInterrupts(); - const int16_t pulses = intPulseCount; - intPulseCount = 0; - interrupts(); - - return pulses; -} - -uint16_t takeSampleLowPower() { - // To save battery power, use light sleep as much as possible. - // During light sleep, no counters or interrupts are working. - // Therefore simply wake up on each pulse signal change. This - // is fast enough for the low frequencies from a Geiger tube - // (below 2kHz): - // Wake up at end of sample period. Also - // wake up on pulse getting high and getting low. - // Waking up directly on rising/falling edges is not possible, - // so wait until level change. - // Switch to interrupt counting while awake for calculations - // and display update. - - // stop interrupt (switch to active wakeup counting loop): - detachInterrupt(PULSE_PIN); - - int32_t remainingWait = calcRemainingWait(); - esp_sleep_wakeup_cause_t cause = ESP_SLEEP_WAKEUP_UNDEFINED; - while (cause != ESP_SLEEP_WAKEUP_TIMER && remainingWait > 0) { - - if (digitalRead(PULSE_PIN)) { - // wait for low pulse start or sample time end - esp_sleep_enable_timer_wakeup(remainingWait); - gpio_wakeup_enable(PULSE_GPIO, GPIO_INTR_LOW_LEVEL); - esp_sleep_enable_gpio_wakeup(); - esp_light_sleep_start(); - cause = esp_sleep_get_wakeup_cause(); - } - - remainingWait = calcRemainingWait(); - if (cause != ESP_SLEEP_WAKEUP_TIMER && remainingWait > 0) { - // wait for high pulse start or sample time end - esp_sleep_enable_timer_wakeup(remainingWait); - gpio_wakeup_enable(PULSE_GPIO, GPIO_INTR_HIGH_LEVEL); - esp_sleep_enable_gpio_wakeup(); - esp_light_sleep_start(); - cause = esp_sleep_get_wakeup_cause(); - if (cause == ESP_SLEEP_WAKEUP_GPIO) { - ++pulseCount; - } - } - - remainingWait = calcRemainingWait(); - } - - // take sample and add to statistics - - sampleStart = micros(); - const int16_t pulses = pulseCount + intPulseCount; - // Serial.print("pc="); - // Serial.print(pulseCount); - // Serial.print(" ipc="); - // Serial.println(intPulseCount); - attachInterrupt(PULSE_PIN, pulse, RISING); - interrupts(); - // reset counters AFTER enabling interrupt to avoid double-counting on high signal - pulseCount = 0; - intPulseCount = 0; - - return pulses; -} - -void loop() { +void loop() +{ // blinky @@ -174,18 +75,22 @@ void loop() { blinky = !blinky; const uint16_t pulses = - wifiSwitchOn() ? takeSampleNoSleep() : takeSampleLowPower(); + wifiSwitchOn() ? takeSampleNoSleep() : takeSampleLowPower(); geigerData.addPulses(pulses); geigerData.nextSample(); - if (wifiSwitchOn()) { + if (wifiSwitchOn()) + { ingestCountdown--; - if (ingestCountdown <= 0) { + if (ingestCountdown <= 0) + { ingestCountdown = ingestInterval; ingest(geigerData, ingestInterval); } - } else { + } + else + { deinitIngest(); } @@ -217,3 +122,102 @@ void loop() { updateDisplay(geigerData, uSphStr, cpmStr); } + +// interrupt handler +void pulse() +{ + ++intPulseCount; +} + +uint32_t calcRemainingWait() +{ + const uint32_t remaining = sampleMicros - (micros() - sampleStart); + return remaining > sampleMicros ? 0 : remaining; +} + +boolean wifiSwitchOn() +{ + return digitalRead(WIFI_SWITCH_PIN) == 0; +} + +uint16_t takeSampleNoSleep() +{ + attachInterrupt(PULSE_PIN, pulse, RISING); + + int32_t remainingWait = calcRemainingWait(); + delayMicroseconds(remainingWait); + sampleStart = micros(); + noInterrupts(); + const int16_t pulses = intPulseCount; + intPulseCount = 0; + interrupts(); + + return pulses; +} + +uint16_t takeSampleLowPower() +{ + // To save battery power, use light sleep as much as possible. + // During light sleep, no counters or interrupts are working. + // Therefore simply wake up on each pulse signal change. This + // is fast enough for the low frequencies from a Geiger tube + // (below 2kHz): + // Wake up at end of sample period. Also + // wake up on pulse getting high and getting low. + // Waking up directly on rising/falling edges is not possible, + // so wait until level change. + // Switch to interrupt counting while awake for calculations + // and display update. + + // stop interrupt (switch to active wakeup counting loop): + detachInterrupt(PULSE_PIN); + + int32_t remainingWait = calcRemainingWait(); + esp_sleep_wakeup_cause_t cause = ESP_SLEEP_WAKEUP_UNDEFINED; + while (cause != ESP_SLEEP_WAKEUP_TIMER && remainingWait > 0) + { + + if (digitalRead(PULSE_PIN)) + { + // wait for low pulse start or sample time end + esp_sleep_enable_timer_wakeup(remainingWait); + gpio_wakeup_enable(PULSE_GPIO, GPIO_INTR_LOW_LEVEL); + esp_sleep_enable_gpio_wakeup(); + esp_light_sleep_start(); + cause = esp_sleep_get_wakeup_cause(); + } + + remainingWait = calcRemainingWait(); + if (cause != ESP_SLEEP_WAKEUP_TIMER && remainingWait > 0) + { + // wait for high pulse start or sample time end + esp_sleep_enable_timer_wakeup(remainingWait); + gpio_wakeup_enable(PULSE_GPIO, GPIO_INTR_HIGH_LEVEL); + esp_sleep_enable_gpio_wakeup(); + esp_light_sleep_start(); + cause = esp_sleep_get_wakeup_cause(); + if (cause == ESP_SLEEP_WAKEUP_GPIO) + { + ++pulseCount; + } + } + + remainingWait = calcRemainingWait(); + } + + // take sample and add to statistics + + sampleStart = micros(); + const int16_t pulses = pulseCount + intPulseCount; + // Serial.print("pc="); + // Serial.print(pulseCount); + // Serial.print(" ipc="); + // Serial.println(intPulseCount); + attachInterrupt(PULSE_PIN, pulse, RISING); + interrupts(); + // reset counters AFTER enabling interrupt to avoid double-counting on high signal + pulseCount = 0; + intPulseCount = 0; + + return pulses; +}