mirror of
https://github.com/grillbaer/esp32-geiger-counter.git
synced 2025-12-21 21:33:03 +01:00
204 lines
5.2 KiB
C++
204 lines
5.2 KiB
C++
#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
|
|
|
|
// 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
|
|
GeigerData geigerData(600, 1, STS6_CPM_PER_USPH);
|
|
|
|
// blinky state
|
|
int blinky = 1;
|
|
|
|
// Pulses counted by interrupt (while CPU is awake)
|
|
volatile uint16_t intPulseCount = 0;
|
|
// Pulses counted during ESP light sleep
|
|
volatile uint16_t pulseCount = 0;
|
|
|
|
// Sample duration in µs
|
|
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!");
|
|
|
|
// OLED
|
|
initDisplay();
|
|
|
|
// blinky
|
|
pinMode(LED_BUILTIN, OUTPUT);
|
|
|
|
// 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
|
|
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() {
|
|
|
|
// 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);
|
|
}
|
|
} else {
|
|
deinitIngest();
|
|
}
|
|
|
|
// determine current value, average 6 seconds
|
|
// because this is very near to the 5 seconds history
|
|
// bar width and gives nicely rounded count values
|
|
|
|
const uint16_t samples = 6;
|
|
const uint32_t prevPulses = geigerData.getPreviousPulses(1, samples);
|
|
const uint32_t cpm = prevPulses * (60 / samples);
|
|
const float uSph = geigerData.toMicroSievertPerHour(prevPulses, samples);
|
|
|
|
// test for display layout:
|
|
// const uint32_t cpm = 1000*60;
|
|
// const float uSph = geigerData.toMicroSievertPerHour(cpm, 60);
|
|
|
|
char cpmStr[16];
|
|
ltoa(cpm, cpmStr, 10);
|
|
char uSphStr[16];
|
|
sprintf(uSphStr, "%.2f", uSph);
|
|
|
|
// serial output cpm and µS/h
|
|
|
|
Serial.print(pulses);
|
|
Serial.print(" ");
|
|
Serial.print(cpmStr);
|
|
Serial.print(" ");
|
|
Serial.println(uSphStr);
|
|
|
|
updateDisplay(geigerData, uSphStr, cpmStr);
|
|
}
|