#include <Arduino.h>
constexpr uint8_t PIN_GEIGER = PA0;
constexpr uint8_t PIN_BUZZER = PA8;
constexpr uint8_t PIN_LED_G = PA9;
constexpr uint8_t PIN_LED_R = PA10;
constexpr uint8_t PIN_LED_A = PA11;
constexpr uint8_t PIN_BTN1 = PC13;
constexpr uint8_t PIN_BTN2 = PA1;
constexpr uint8_t PIN_BTN3 = PA4;
constexpr unsigned long HEARTBEAT_MS = 1000;
constexpr unsigned long BUTTON_DEBOUNCE_MS = 40;
constexpr unsigned long CLICK_MS = 8;
constexpr unsigned long PULSE_DEADTIME_US = 120;
constexpr uint16_t DEFAULT_ALARM_CPM = 120;
constexpr uint16_t MIN_ALARM_CPM = 25;
constexpr uint16_t MAX_ALARM_CPM = 5000;
constexpr uint16_t ALARM_STEP = 25;
constexpr float CPM_PER_USVH = 175.0f;
volatile uint32_t isrTotalCounts = 0;
volatile uint16_t isrSecondCounts = 0;
volatile uint32_t isrPendingClicks = 0;
volatile uint32_t lastPulseMicros = 0;
uint16_t cpmBuckets[60] = {0};
uint8_t currentBucket = 0;
uint32_t rollingCPM = 0;
uint32_t totalCounts = 0;
uint16_t currentSecondCounts = 0;
uint16_t alarmCPM = DEFAULT_ALARM_CPM;
bool muteClicks = false;
bool alarmActive = false;
unsigned long lastSecondTick = 0;
unsigned long lastHeartbeatToggle = 0;
unsigned long lastSerialPrint = 0;
bool heartbeatState = false;
bool buzzerOn = false;
unsigned long buzzerOffAt = 0;
struct ButtonState {
uint8_t pin;
bool lastStable;
bool lastRead;
unsigned long lastChangeMs;
};
ButtonState btn1 { PIN_BTN1, HIGH, HIGH, 0 };
ButtonState btn2 { PIN_BTN2, HIGH, HIGH, 0 };
ButtonState btn3 { PIN_BTN3, HIGH, HIGH, 0 };
void geigerISR() {
uint32_t nowUs = micros();
if ((nowUs - lastPulseMicros) < PULSE_DEADTIME_US) {
return;
}
lastPulseMicros = nowUs;
isrTotalCounts++;
isrSecondCounts++;
isrPendingClicks++;
}
bool pollButtonPress(ButtonState &btn) {
bool reading = digitalRead(btn.pin);
if (reading != btn.lastRead) {
btn.lastChangeMs = millis();
btn.lastRead = reading;
}
if ((millis() - btn.lastChangeMs) > BUTTON_DEBOUNCE_MS) {
if (reading != btn.lastStable) {
btn.lastStable = reading;
if (btn.lastStable == LOW) {
return true;
}
}
}
return false;
}
void startClick() {
if (muteClicks) return;
digitalWrite(PIN_BUZZER, HIGH);
buzzerOn = true;
buzzerOffAt = millis() + CLICK_MS;
}
void updateBuzzer() {
if (buzzerOn && millis() >= buzzerOffAt) {
digitalWrite(PIN_BUZZER, LOW);
buzzerOn = false;
}
bool shouldClick = false;
noInterrupts();
if (isrPendingClicks > 0 && !buzzerOn) {
isrPendingClicks--;
shouldClick = true;
}
interrupts();
if (shouldClick) {
startClick();
}
}
void updateOneSecondStats() {
if (millis() - lastSecondTick < 1000) return;
lastSecondTick += 1000;
noInterrupts();
currentSecondCounts = isrSecondCounts;
isrSecondCounts = 0;
totalCounts = isrTotalCounts;
interrupts();
rollingCPM -= cpmBuckets[currentBucket];
cpmBuckets[currentBucket] = currentSecondCounts;
rollingCPM += cpmBuckets[currentBucket];
currentBucket = (currentBucket + 1) % 60;
alarmActive = (rollingCPM >= alarmCPM);
}
void updateIndicators() {
if (millis() - lastHeartbeatToggle >= HEARTBEAT_MS) {
lastHeartbeatToggle = millis();
heartbeatState = !heartbeatState;
}
digitalWrite(PIN_LED_G, heartbeatState ? HIGH : LOW);
digitalWrite(PIN_LED_R, alarmActive ? HIGH : LOW);
digitalWrite(PIN_LED_A, muteClicks ? HIGH : LOW);
}
void handleButtons() {
if (pollButtonPress(btn1)) {
muteClicks = !muteClicks;
if (muteClicks) {
digitalWrite(PIN_BUZZER, LOW);
buzzerOn = false;
}
}
if (pollButtonPress(btn2)) {
if (alarmCPM + ALARM_STEP <= MAX_ALARM_CPM) {
alarmCPM += ALARM_STEP;
} else {
alarmCPM = MAX_ALARM_CPM;
}
}
if (pollButtonPress(btn3)) {
if (alarmCPM >= (MIN_ALARM_CPM + ALARM_STEP)) {
alarmCPM -= ALARM_STEP;
} else {
alarmCPM = MIN_ALARM_CPM;
}
}
}
float computeUSvPerHour(uint32_t cpm) {
return cpm / CPM_PER_USVH;
}
void printStatus() {
if (millis() - lastSerialPrint < 1000) return;
lastSerialPrint = millis();
Serial.print("CPM: ");
Serial.print(rollingCPM);
Serial.print(" | Total: ");
Serial.print(totalCounts);
Serial.print(" | Alarm: ");
Serial.print(alarmCPM);
Serial.print(" | uSv/h: ");
Serial.print(computeUSvPerHour(rollingCPM), 2);
Serial.print(" | Mute: ");
Serial.print(muteClicks ? "ON" : "OFF");
Serial.print(" | Alarm State: ");
Serial.println(alarmActive ? "HOT" : "OK");
}
void setupPins() {
pinMode(PIN_GEIGER, INPUT_PULLUP);
pinMode(PIN_BUZZER, OUTPUT);
digitalWrite(PIN_BUZZER, LOW);
pinMode(PIN_LED_G, OUTPUT);
pinMode(PIN_LED_R, OUTPUT);
pinMode(PIN_LED_A, OUTPUT);
digitalWrite(PIN_LED_G, LOW);
digitalWrite(PIN_LED_R, LOW);
digitalWrite(PIN_LED_A, LOW);
pinMode(PIN_BTN1, INPUT_PULLUP);
pinMode(PIN_BTN2, INPUT_PULLUP);
pinMode(PIN_BTN3, INPUT_PULLUP);
}
void setupInterrupts() {
attachInterrupt(PIN_GEIGER, geigerISR, FALLING);
}
void setup() {
setupPins();
Serial.begin(115200);
delay(200);
Serial.println("MANTA Rev A startup");
Serial.println("BTN1 = mute");
Serial.println("BTN2 = alarm up");
Serial.println("BTN3 = alarm down");
digitalWrite(PIN_LED_G, HIGH);
digitalWrite(PIN_LED_R, HIGH);
digitalWrite(PIN_LED_A, HIGH);
delay(250);
digitalWrite(PIN_LED_G, LOW);
digitalWrite(PIN_LED_R, LOW);
digitalWrite(PIN_LED_A, LOW);
setupInterrupts();
lastSecondTick = millis();
lastHeartbeatToggle = millis();
lastSerialPrint = 0;
}
void loop() {
handleButtons();
updateOneSecondStats();
updateBuzzer();
updateIndicators();
printStatus();
}