Radioaktive Strahlung mit ESP32 und Geigerzähler messen

Zum Abschluss des Jahres ein nicht ganz so ernsthaftes Projekt. Kürzlich war auf AliExpress Black Friday, und dabei ist mir eine kleine Platine aufgefallen, mit der sich radioaktive Strahlung messen lässt. Das Board inklusive Zählrohr kostete nur rund 20 Euro und da konnte ich einfach nicht widerstehen.

Es handelt sich um ein RadiationD-v1.1-Board mit einem Geiger-Müller-Zählrohr, das akkustische und elektrische Impule ausgibt. Über die Hintergründe dieses Boards weiß ich recht wenig, aber es gibt ein GitHub Repository mit vielen nützlichen Informationen, darunter auch ein Arduino Beispiel zur Anzeige der Strahlungswerte. Da ich aber beinahe ausschließlich mit dem ESP-IDF Framework entwickle, habe ich eine einfache Komponente für den ESP32 geschrieben, die sich einfach in eigene Projekte einbinden lässt.

Was kann man mit dem RadiationD-v1.1-Board messen?

Ein Geiger-Müller-Zählrohr misst weder die Energie, die Teilchenart noch die Richtung der Strahlung. Stattdessen detektiert es lediglich einzelne ionisierende Ereignisse. Jedes Mal, wenn ein energiereiches Teilchen oder Photon das Zählrohr durchdringt und das Gas im Inneren ionisiert, entsteht ein kurzer elektrischer Impuls. Das RadiationD-v1.1-Board bereitet diese Ereignisse auf und gibt sie in zwei Formen aus:
akustisch als „Klicks“ und elektrisch als digitale Impulse, die sich mit einem Mikrocontroller wie dem ESP32 einfach zählen lassen. 

RadiationD-v1.1

Die Anzahl der Impulse pro Zeit, auch Zählrate genannt wird üblicherweise in Counts per Minute (CPM) angegeben. Je höher die Zählrate, desto mehr ionisierende Strahlung trifft in diesem Moment auf das Zählrohr. Schon ohne weitere Umrechnung lässt sich damit gut erkennen:

  • ob die Strahlung gegenüber der natürlichen Hintergrundstrahlung erhöht ist
  • ob sich die Strahlung zeitlich verändert
  • ob eine Strahlenquelle näher oder weiter entfernt ist

Für viele Experimente und Vergleiche ist diese relative Messung bereits vollkommen ausreichend. Üblicherweise wird Strahlung aber in Mikrosievert pro Stunde (µSv/h) angegeben. Diese Größe beschreibt die Dosisleistung, also wie stark ionisierende Strahlung auf den menschlichen Körper wirkt.

Ein Geiger-Müller-Zählrohr kann diese Größe nicht direkt messen. Stattdessen wird sie aus der Zählrate abgeschätzt:

Dosisleistung = CPM × Umrechnungsfaktor

Dieser Umrechnungsfaktor hängt unter anderem ab von:

  • dem verwendeten Zählrohr
  • der Art der Strahlung (Gamma, Beta)
  • der Energie der Strahlung

Dieser Faktor ist daher nur eine grobe Näherung. Die angezeigten µSv/h-Werte eignen sich zwar gut zur Orientierung, sollten aber nicht als präzise Dosismessung verstanden werden.

Das im RadiationD-v1.1-Board verbaute Geiger-Müller-Zählrohr detektiert hauptsächlich Gamma-Strahlung und hochenergetische Beta-Strahlung. Alpha-Strahlung wird dagegen kaum erfasst, da sie bereits durch das Gehäuse, die Luft oder die Zählrohrwand abgeschirmt wird. Der Umrechnungsfaktor von CPM zu µSv/h für das verwendete J321 Zählrohr (ein Nachbau der M4011 Röhre) beträgt 0,0066 µSv/h pro CPM, d.h. 151 CPM entsprechen etwa 1 µSv/h. Auf Matt's Blog wird hier aber ein Wert von 153.8 angegeben.

Damit eignet sich das Board gut zur Messung von:

  • natürlicher Hintergrundstrahlung
  • leicht erhöhter Umgebungsstrahlung
  • radioaktiven Materialien mit Gamma- oder Beta-Emission

Nicht messbar ist dagegen:

  • Art der Strahlung im Detail
  • Energie der Teilchen
  • exakte biologische Dosis

Das RadiationD-v1.1-Board eignet sich daher hervorragend für Experimente, Langzeitmessungen und Lernprojekte, bei denen es weniger um absolute Genauigkeit, sondern um das Verständnis von Strahlung und Messprinzipien geht. 

Strahlung mit dem ESP32 auswerten

Zur Auswertung der Messimpulse wird das RadiationD-v1.1-Board mit einem ESP32 verbunden. Elektrisch ist die Anbindung erfreulich einfach: Neben der Spannungsversorgung benötigt man lediglich einen digitalen Eingang, um die Impulse des Geiger-Müller-Zählrohrs zu erfassen.

Die Stromversorgung des RadiationD-v1.1 erfolgt über die 5 V und GND Ping des ESP32. Der mit Vin beschriftete Pin ist etwas irreführend. Entgegen der Beschriftung handelt es sich nicht um einen Eingang, sondern um den Impulsausgang des Geigerzählers. An diesem Pin liegen die vom Board erzeugten Zählimpulse an. In meinem Aufbau ist dieser Ausgang mit GPIO 4 des ESP32 verbunden. 

ESP32 und RadiationD-v1.1

Im Ruhezustand liegt der Ausgang des RadiationD-v1.1-Boards auf High. Wird im Zählrohr ein ionisierendes Ereignis detektiert, erzeugt das Board einen kurzen Low-Puls. Jeder dieser Pulse entspricht genau einem registrierten Strahlungsereignis. Aus diesem Grund wird der GPIO des ESP32 auf fallende Flanke konfiguriert. So wird jeder Impuls zuverlässig erfasst, unabhängig davon, wie kurz er ist.

Die Initialisierung des Eingangs erfolgt über die GPIO-Interrupt-Funktionalität des ESP32. Der Pin wird als Eingang konfiguriert, interne Pullups oder Pulldowns sind nicht erforderlich, da das Board den Pegel aktiv treibt.

    // GPIO Setup
    gpio_config_t io_conf = {};
    io_conf.intr_type = GPIO_INTR_NEGEDGE;
    io_conf.pin_bit_mask = (1ULL << config->gpio_pin);
    io_conf.mode = GPIO_MODE_INPUT;
    // No pullup here!
    ESP_ERROR_CHECK(gpio_config(&io_conf));

Wichtig ist dabei die Auswahl des Interrupt-Typs auf GPIO_INTR_NEGEDGE, um auf die fallende Flanke zu reagieren.

Jeder erkannte Impuls löst einen Interrupt aus, der in einer kurzen Interrupt-Service-Routine verarbeitet wird.
Da ISR-Funktionen möglichst schnell ausgeführt werden sollten, beschränkt sich die Routine auf das atomare Inkrementieren eines Zählers.

// ISR Handler
static void IRAM_ATTR geiger_isr_handler(void* arg) {

    atomic_fetch_add(&pulse_count, 1);
}

Die eigentliche Auswertung der Messdaten erfolgt außerhalb des Interrupt-Kontexts in einem separaten FreeRTOS-Task. Dieser Task läuft mit einer festen Periodendauer von einer Sekunde und übernimmt drei Aufgaben:

Atomarer Zugriff auf die Impulse: Einmal pro Sekunde wird der aktuelle Zählerstand atomar ausgelesen und gleichzeitig zurückgesetzt.

Gleitender Mittelwert (Rolling Average): Die pro Sekunde erfassten Ereignisse werden in einem Ringbuffer gespeichert. Über die konfigurierbare Länge dieses Buffers wird ein gleitender Mittelwert berechnet. Ein großer Buffer reduziert statistische Schwankungen, reagiert aber träge auf Änderungen. Ein kleiner Buffer reagiert schneller, zeigt dafür stärkere Schwankungen.

Berechnung der Messwerte: Aus der aufsummierten Zählrate wird die aktuelle CPM-Zahl berechnet und anschließend mithilfe eines Umrechnungsfaktors eine abgeschätzte Dosisleistung in µSv/h bestimmt.

// Background task 
static void geiger_task(void *pvParameters) {
    int buffer_index = 0;
    uint32_t total_counts_in_period = 0;

    // Initialize buffer (allocate memory)
    rolling_buffer = calloc(active_config.rolling_avg_seconds, sizeof(uint32_t));
    if (rolling_buffer == NULL) {
        ESP_LOGE(TAG, "No memory for rolling buffer!");
        vTaskDelete(NULL);
    }

    while (1) {
        // Wait 1 second
        vTaskDelay(pdMS_TO_TICKS(1000));

        // 1. Atomically retrieve and reset the counter
        uint32_t counts_this_second = atomic_exchange(&pulse_count, 0);

        // 2. Rolling Average Logic
        total_counts_in_period -= rolling_buffer[buffer_index];
        total_counts_in_period += counts_this_second;
        rolling_buffer[buffer_index] = counts_this_second;

        buffer_index = (buffer_index + 1) % active_config.rolling_avg_seconds;

        // 3. Calculation
        float cpm = (float)total_counts_in_period * (60.0f / (float)active_config.rolling_avg_seconds);
        
        // Write values to global (static) variables
        current_cpm = cpm;
        current_usvh = cpm / active_config.conversion_factor;
        
    }
}

Die Einbindung der Geiger-Komponente in ein eigenes ESP-IDF-Projekt ist sehr einfach gehalten.
Über eine Konfigurationsstruktur werden:

  • das verwendete GPIO-Pin
  • der Umrechnungsfaktor von CPM zu µSv/h
  • die Größe des Rolling-Average-Fensters

festgelegt.

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"
#include "driver/gpio.h"

#include "geiger.h"

static const char *TAG = "APP";

void app_main(void) {
    // 1. Create configuration
    geiger_config_t config = {
        .gpio_pin = GPIO_NUM_4,
        .conversion_factor = 153.8f, // https://muman.ch/muman/muman-geiger-counter.htm  or 151.0 ?
        .rolling_avg_seconds = 600 
    };

    // 2. Initialize component
    geiger_init(&config);

    // 3. Main loop: Do whatever you want here
    // (Display data, send via MQTT, write to screen)
    while (1) {
        // We just read the calculated values
        float usvh = geiger_get_usvh();
        float cpm = geiger_get_cpm();

        ESP_LOGI(TAG, "Measurement: %.2f CPM | %.4f µSv/h", cpm, usvh);

        // Insert code here, e.g.: send_to_mqtt(usvh);
        
        vTaskDelay(pdMS_TO_TICKS(5000)); // Logging every 5 seconds is sufficient
    }
}

Während ich diesen Text schreibe, liefert der ESP32 folgende Ausgabe:

I (1180280) APP: Measurement: 23.10 CPM | 0.1502 µSv/h
I (1185280) APP: Measurement: 23.20 CPM | 0.1508 µSv/h
I (1190280) APP: Measurement: 23.30 CPM | 0.1515 µSv/h
I (1195280) APP: Measurement: 23.20 CPM | 0.1508 µSv/h
I (1200280) APP: Measurement: 23.40 CPM | 0.1521 µSv/h
I (1205280) APP: Measurement: 23.30 CPM | 0.1515 µSv/h
I (1210280) APP: Measurement: 23.60 CPM | 0.1534 µSv/h
I (1215280) APP: Measurement: 23.10 CPM | 0.1502 µSv/h
I (1220280) APP: Measurement: 22.90 CPM | 0.1489 µSv/h
I (1225280) APP: Measurement: 22.60 CPM | 0.1469 µSv/h
I (1230280) APP: Measurement: 22.50 CPM | 0.1463 µSv/h
I (1235280) APP: Measurement: 22.70 CPM | 0.1476 µSv/h
I (1240280) APP: Measurement: 22.40 CPM | 0.1456 µSv/h
I (1245280) APP: Measurement: 22.10 CPM | 0.1437 µSv/h
I (1250280) APP: Measurement: 22.00 CPM | 0.1430 µSv/h
I (1255280) APP: Measurement: 22.20 CPM | 0.1443 µSv/h
I (1260280) APP: Measurement: 22.30 CPM | 0.1450 µSv/h

Diese Werte liegen glücklicherweise im Bereich der typischen natürlichen Hintergrundstrahlung.
Die leichten Schwankungen sind kein Messfehler, sondern eine direkte Folge der statistischen Natur radioaktiver Zerfälle. Selbst bei konstanter Strahlung treten Ereignisse zufällig auf, was sich insbesondere bei niedrigen Zählraten bemerkbar macht.

Fazit

Trotz des sehr günstigen Preises liefert das RadiationD-v1.1-Board stabile und nachvollziehbare Messwerte im Bereich der natürlichen Hintergrundstrahlung. Wichtig ist jedoch, die Grenzen dieser Lösung zu kennen: Das Board misst ausschließlich Ereignisse, nicht die Energie oder Art der Strahlung, und die Umrechnung in µSv/h stellt lediglich eine Näherung dar. Für sicherheitsrelevante oder kalibrierte Dosismessungen ist ein solches System daher nicht geeignet.

Leider habe ich im Moment kein Material, mit dem ich eine erhöhte Strahlung messen kann. Im Internet findet man zwar einige Informationen dazu, aber ich habe weder Uranglas noch einen Rauchmelder. Vielleicht hat ja jemand einen Tipp für mich?

Da der Hardwareaufwand wirklich sehr gering ist, überlege ich sogar, den Geigerzähler in meine Wetterstation einzubauen. Man kann dann nur hoffen, dort niemals einen wirklich hohen Wert zu sehen. Der Source Code der Geigerzähler-Komponente steht wie immer auf GitHub bereit.

Konversation wird geladen