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_ */