mirror of
https://github.com/grillbaer/esp32-geiger-counter.git
synced 2025-12-24 06:28:44 +01:00
Initial import
This commit is contained in:
233
ESP32GeigerCounter.ino
Normal file
233
ESP32GeigerCounter.ino
Normal file
@@ -0,0 +1,233 @@
|
||||
#include "Arduino.h"
|
||||
#include "U8g2lib.h"
|
||||
|
||||
#include "driver/pcnt.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/rtc_io.h"
|
||||
|
||||
#include "GeigerData.h"
|
||||
|
||||
// ~400<30>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);
|
||||
|
||||
// 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 <20>s
|
||||
const uint32_t sampleMicros = geigerData.sampleSeconds * 1000000;
|
||||
// Absolute sample interval start micros
|
||||
uint32_t sampleStart = 0;
|
||||
|
||||
void setup() {
|
||||
Serial.begin(921600);
|
||||
|
||||
// high I2c clock still results in about 100ms buffer transmission to OLED:
|
||||
u8g2.setBusClock(1000000);
|
||||
u8g2.begin();
|
||||
|
||||
// blinky
|
||||
pinMode(LED_BUILTIN, OUTPUT);
|
||||
|
||||
// Geiger pulse input
|
||||
pinMode(PULSE_PIN, INPUT);
|
||||
|
||||
// initialize sample start
|
||||
sampleStart = micros();
|
||||
}
|
||||
|
||||
// interrupt handler
|
||||
void pulse() {
|
||||
++intPulseCount;
|
||||
}
|
||||
|
||||
uint32_t calcRemainingWait() {
|
||||
return sampleMicros - (micros() - sampleStart);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
|
||||
// blinky
|
||||
|
||||
digitalWrite(LED_BUILTIN, blinky);
|
||||
blinky = !blinky;
|
||||
|
||||
// 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;
|
||||
|
||||
geigerData.addPulses(pulses);
|
||||
geigerData.nextSample();
|
||||
|
||||
// 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 <20>S/h
|
||||
|
||||
Serial.print(pulses);
|
||||
Serial.print(" ");
|
||||
Serial.print(cpmStr);
|
||||
Serial.print(" ");
|
||||
Serial.println(uSphStr);
|
||||
|
||||
// render cpm and <20>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("<EFBFBD>S/h");
|
||||
u8g2.setCursor(xUSph - w, y);
|
||||
u8g2.print("<EFBFBD>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();
|
||||
}
|
||||
Reference in New Issue
Block a user