diff --git a/.cproject b/.cproject index 4a0676d..07ce0be 100644 --- a/.cproject +++ b/.cproject @@ -19,13 +19,6 @@ @@ -96,4 +78,5 @@ + diff --git a/.gitignore b/.gitignore index 74116b9..c3c1331 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /Release/ /sloeber.ino.cpp /spec.d +/credentials.h diff --git a/.project b/.project index 4d16d75..330704e 100644 --- a/.project +++ b/.project @@ -61,6 +61,11 @@ 2 ECLIPSE_HOME/arduinoPlugin/libraries/U8g2/2.25.10 + + libraries/WiFi + 2 + C:/dev/git/arduino-esp32/libraries/WiFi + libraries/Wire 2 diff --git a/ESP32GeigerCounter.ino b/ESP32GeigerCounter.ino index b6fdcfc..5dbcab5 100644 --- a/ESP32GeigerCounter.ino +++ b/ESP32GeigerCounter.ino @@ -1,19 +1,19 @@ #include "Arduino.h" -#include "U8g2lib.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 -// OLED display 128x64 with SH1106 controller -// on I2C GPIOs SCL 22 and SDA 21 -U8G2_SH1106_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, 22, 21); +// switch input for WiFi on (low) and off (high) +#define WIFI_SWITCH_PIN 4 // Keep 600 samples of 1s in history (10 minutes), // calculate radiation for russian STS-6 ("CTC-6") Geiger tube @@ -32,12 +32,15 @@ const uint32_t sampleMicros = geigerData.sampleSeconds * 1000000; // Absolute sample interval start micros uint32_t sampleStart = 0; +const int16_t ingestInterval = 60; +int16_t ingestCountdown; + void setup() { Serial.begin(921600); + Serial.println("Starting!"); - // high I2c clock still results in about 100ms buffer transmission to OLED: - u8g2.setBusClock(1000000); - u8g2.begin(); + // OLED + initDisplay(); // blinky pinMode(LED_BUILTIN, OUTPUT); @@ -45,8 +48,16 @@ void setup() { // Geiger pulse input pinMode(PULSE_PIN, INPUT); + // WiFi switch input + pinMode(WIFI_SWITCH_PIN, INPUT_PULLUP); + + if (wifiSwitchOn()) { + initIngest(); + } + // initialize sample start sampleStart = micros(); + ingestCountdown = ingestInterval; } // interrupt handler @@ -55,16 +66,29 @@ void pulse() { } uint32_t calcRemainingWait() { - return sampleMicros - (micros() - sampleStart); + const uint32_t remaining = sampleMicros - (micros() - sampleStart); + return remaining > sampleMicros ? 0 : remaining; } -void loop() { +boolean wifiSwitchOn() { + return digitalRead(WIFI_SWITCH_PIN) == 0; +} - // blinky +uint16_t takeSampleNoSleep() { + attachInterrupt(PULSE_PIN, pulse, RISING); - digitalWrite(LED_BUILTIN, blinky); - blinky = !blinky; + 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 @@ -123,9 +147,30 @@ void loop() { pulseCount = 0; intPulseCount = 0; + return pulses; +} + +void loop() { + + // blinky + + digitalWrite(LED_BUILTIN, blinky); + blinky = !blinky; + + const uint16_t pulses = + wifiSwitchOn() ? takeSampleNoSleep() : takeSampleLowPower(); + geigerData.addPulses(pulses); geigerData.nextSample(); + if (wifiSwitchOn()) { + ingestCountdown--; + if (ingestCountdown <= 0) { + ingestCountdown = ingestInterval; + ingest(geigerData, ingestInterval); + } + } + // determine current value, average 6 seconds // because this is very near to the 5 seconds history // bar width and gives nicely rounded count values @@ -152,82 +197,5 @@ void loop() { Serial.print(" "); Serial.println(uSphStr); - // render cpm and µS/h displays - - u8g2.clearBuffer(); - uint16_t y = 14; - uint16_t xCpm = 56; - uint16_t xUSph = 127; - u8g2.setFont(u8g2_font_crox4hb_tr); - - u8g2_uint_t w = u8g2.getStrWidth(uSphStr); - u8g2.setCursor(xUSph - w, y); - u8g2.print(uSphStr); - - w = u8g2.getStrWidth(cpmStr); - u8g2.setCursor(xCpm - w, y); - u8g2.print(cpmStr); - - y = 21; - u8g2.setFont(u8g2_font_4x6_tf); - w = u8g2.getStrWidth("µS/h"); - u8g2.setCursor(xUSph - w, y); - u8g2.print("µS/h"); - w = u8g2.getStrWidth("cnt/min"); - u8g2.setCursor(xCpm - w, y); - u8g2.print("cnt/min"); - - // history bar graph - - 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); - - // 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++) { - const uint32_t prevPulses = geigerData.getPreviousPulses(offset, - samplesPerBar); - if (prevPulses > maxPulses) - maxPulses = prevPulses; - offset += samplesPerBar; - } - const float maxUSph = geigerData.toMicroSievertPerHour(maxPulses, - 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) { - u8g2.setCursor(0, 63 - i + 3); - if (uSphPerPixel >= 0.1) - sprintf(s, "%.0f", i * uSphPerPixel); - else - sprintf(s, ".%.0f", i * uSphPerPixel * 10); - u8g2.print(s); - 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++) { - const uint32_t prevPulses = geigerData.getPreviousPulses(offset, - samplesPerBar); - const float uSph = geigerData.toMicroSievertPerHour(prevPulses, - samplesPerBar); - offset += samplesPerBar; - uint16_t barHeight = 1 + (int) (uSph / uSphPerPixel); - if (barHeight > 40) - barHeight = 40; - u8g2.drawVLine(127 - i, 63 - barHeight, barHeight); - } - - u8g2.sendBuffer(); + updateDisplay(geigerData, uSphStr, cpmStr); } diff --git a/display.cpp b/display.cpp new file mode 100644 index 0000000..9c60774 --- /dev/null +++ b/display.cpp @@ -0,0 +1,100 @@ +#include "U8g2lib.h" + +#include "display.h" + +// OLED display 128x64 with SH1106 controller +// on I2C GPIOs SCL 22 and SDA 21 +U8G2_SH1106_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, 22, 21); + +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]) { + uint16_t y = 14; + uint16_t xCpm = 56; + uint16_t xUSph = 127; + + u8g2.setFont(u8g2_font_crox4hb_tr); + u8g2_uint_t w = u8g2.getStrWidth(uSphStr); + u8g2.setCursor(xUSph - w, y); + u8g2.print(uSphStr); + + w = u8g2.getStrWidth(cpmStr); + u8g2.setCursor(xCpm - w, y); + u8g2.print(cpmStr); + + y = 21; + u8g2.setFont(u8g2_font_4x6_tf); + w = u8g2.getStrWidth("µS/h"); + u8g2.setCursor(xUSph - w, y); + u8g2.print("µS/h"); + + w = u8g2.getStrWidth("cnt/min"); + u8g2.setCursor(xCpm - w, y); + u8g2.print("cnt/min"); +} + +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); + + // 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++) { + const uint32_t prevPulses = geigerData.getPreviousPulses(offset, + samplesPerBar); + if (prevPulses > maxPulses) + maxPulses = prevPulses; + + offset += samplesPerBar; + } + + const float maxUSph = geigerData.toMicroSievertPerHour(maxPulses, + 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) { + u8g2.setCursor(0, 63 - i + 3); + if (uSphPerPixel >= 0.1) + sprintf(s, "%.0f", i * uSphPerPixel); + else + sprintf(s, ".%.0f", i * uSphPerPixel * 10); + + u8g2.print(s); + 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++) { + const uint32_t prevPulses = geigerData.getPreviousPulses(offset, + samplesPerBar); + const float uSph = geigerData.toMicroSievertPerHour(prevPulses, + samplesPerBar); + offset += samplesPerBar; + uint16_t barHeight = 1 + (int) ((uSph / uSphPerPixel)); + if (barHeight > 40) + barHeight = 40; + + u8g2.drawVLine(127 - i, 63 - barHeight, barHeight); + } +} + +void updateDisplay(GeigerData &geigerData, char uSphStr[16], char cpmStr[16]) { + u8g2.clearBuffer(); + renderDigits(uSphStr, cpmStr); + renderHistoryBarGraph(geigerData); + u8g2.sendBuffer(); +} diff --git a/display.h b/display.h new file mode 100644 index 0000000..cee0649 --- /dev/null +++ b/display.h @@ -0,0 +1,9 @@ +#ifndef DISPLAY_H_ +#define DISPLAY_H_ + +#include "GeigerData.h" + +void initDisplay(); +void updateDisplay(GeigerData &geigerData, char uSphStr[16], char cpmStr[16]); + +#endif /* DISPLAY_H_ */ diff --git a/ingest.cpp b/ingest.cpp new file mode 100644 index 0000000..43794b9 --- /dev/null +++ b/ingest.cpp @@ -0,0 +1,84 @@ +#include + +#include "GeigerData.h" + +// WiFi and thingspeak credentials: +//const char *wifiSsid = "..."; +//const char *wifiPassword = "..."; +//const char *thingspeakApiKey = "..."; +#include "credentials.h" + +const char *thingsPeakUrl = "api.thingspeak.com"; + +void connect() { + wl_status_t status = WL_DISCONNECTED; + + while (status != WL_CONNECTED) { + WiFi.begin(wifiSsid, wifiPassword); + Serial.print("Connecting to WiFi '"); + Serial.print(wifiSsid); + Serial.println("' ..."); + uint16_t waitRemaining = 10; + while ((status = WiFi.status()) != WL_CONNECTED && waitRemaining > 0) { + Serial.print(" status="); + Serial.println(status); + delay(500); + waitRemaining--; + } + } + + Serial.println("WiFi connected!"); +} + +void initIngest() { + connect(); +} + +void ingest(GeigerData &geigerData, uint16_t intervalSamples) { + WiFiClient client; + if (!client.connect(thingsPeakUrl, 80)) { + Serial.print("Connecting to '"); + Serial.print(thingsPeakUrl); + Serial.println("' failed"); + } else { + + const uint32_t pulses = geigerData.getPreviousPulses(1, + intervalSamples); + const uint32_t cpm = uint32_t( + pulses + / ((float) intervalSamples * geigerData.sampleSeconds + / 60.) + 0.5); + const float uSph = geigerData.toMicroSievertPerHour(pulses, + intervalSamples); + + const String content = "api_key=" + String(thingspeakApiKey) + + "&field1=" + String(cpm) + "&field2=" + String(uSph); + + Serial.println("Ingesting"); + + client.print("POST /update HTTP/1.1\n"); + + client.print("Host: "); + client.print(thingsPeakUrl); + client.print("\n"); + + client.print("Connection: close\n"); + + client.print("Content-Type: application/x-www-form-urlencoded\n"); + client.print("Content-Length: "); + client.print(content.length()); + client.print("\n\n"); + + client.print(content); + delay(1000); + + Serial.print("Response: "); + while (client.available()) { + char c = client.read(); + Serial.write(c); + } + Serial.println(); + + client.stop(); + } +} diff --git a/ingest.h b/ingest.h new file mode 100644 index 0000000..f34bda3 --- /dev/null +++ b/ingest.h @@ -0,0 +1,9 @@ +#ifndef INGEST_H_ +#define INGEST_H_ + +#include "GeigerData.h" + +void initIngest(); +void ingest(GeigerData &geigerData, uint16_t intervalSamples); + +#endif /* INGEST_H_ */