盖革计数器十秒零计数为何不代表零辐射

2026-11-151

盖革计数器连续十秒没有响,屏幕显示 0.0000,能据此判断周围没有辐射吗?先别盯着小数位。真正决定这次读数分量的,是收到了多少个脉冲、实际观察了多久,以及采集有没有中断。

这次给梁山派盖革计数器的计数链路增加一个独立的 C11 统计模块。它保留累计计数,用真实毫秒差计算 CPM;刚开机按已观察时长显示,采集间断就重新建立基线,零计数时另外给出统计上限。实现不改高压电路,验证范围是软件计算与边界行为。

同版本EDA模型局部

图 1 辐射检测扩展板 V1.0.0 的 EDA 三维渲染局部,用于辨认管座、屏幕和按键的位置。这是模型,不是改版实物照。

先找准一个脉冲从哪里进来

工程的 geiger_counter 图页中,盖革管输出经过 R62、R63 和 Q3,Q3 集电极由 R10 拉到 3.3V,网络名是 MCU_GPIO,随后连接 PE6。对应软件采用 EXTI6 下降沿计数。主板连接器 H1 的 33 脚也是 PE6,这两处名称必须对得上。

Q3低压输出局部

图 2 从实际 EDA 导出的 Q3 输出段。R10 为 10kΩ,R62 为 22kΩ,R63 为 100kΩ;程序统计的是低压逻辑脉冲。

H1与PE6连接

图 3 同版本主图的 H1 接口,33 脚连接 PE6。接线检查应同时核对接口编号、方向和地线。

中断里只累加累计计数并清标志;蜂鸣、界面刷新和统计计算放到主循环或任务中。中断里调用名称像 beep 的函数,不等于它一定阻塞;要查看实现,不能仅凭函数名判断。但把耗时工作与计数入口分开,会让计数链路更容易检查。

十秒三次,和两秒三次,是两种读数

计数 N、实际观察时间 T 秒,对应计数率为 CPM = 60 × N / T。固定乘 6 的前提是这批计数确实覆盖完整十秒。

刚开机只观察两秒就收到 3 个脉冲,按已有两秒估计是 90 CPM;若把其余八秒当成已经观察过的零值,固定乘 6 就只得到 18 CPM。反过来,刷新界面花了时间,却仍把每次处理当成严格一秒,也会把计算的分母写错。

参考代码的处理流程是在显示前关闭定时器、显示后清零并重开。由此能确定的是计时口径需要复核,不能把显示耗时当成已经测得的固定数值。这里改成持续运行的单调时间基准,统计只读快照,不为了刷新界面停掉计数和计时。

模块保留最近 10 段有效快照间隔。约每秒调用时,窗口约为十秒;调用有抖动时就显示真实累计时长,不能继续把它叫作精确十秒窗口。累计计数不在每次刷新时清零,避免读数和清零之间漏掉一个脉冲。

0 CPM 仍然有观察时间的边界

在稳定、相互独立的泊松计数模型下,N 个计数的统计标准差约为 sqrt(N)。N 大于零时,相对标准差约为 1 / sqrt(N)。10 个计数约 32%,100 个才约 10%;屏幕多显示几位小数不会增加收到的计数。

零计数不能套用这个相对误差公式。在观察时间 T 内,若平均计数为 λ,收到零次的概率是 exp(-λ)。令这个概率等于 0.05,可得零计数的一侧 95% 统计上限 -ln(0.05) / T,换成 CPM 再乘 60。

十秒零计数时,上限约为 17.97 CPM;六十秒零计数时约为 3.00 CPM。这不是“实际值有 95% 概率小于它”的断言,也不是辐射安全结论,而是上述模型下的频率学统计上限。源强变化、探测效率、噪声、死时间和校准误差都没有被这个公式消除。

CPM 是探测到的计数率。换成 μSv/h 还需要与盖革管、粒子能谱和几何条件匹配的标定。参考程序中的常数 210 不能直接当作另一只管子的通用系数;本模块只输出 CPM 和计数统计量。

把正常零计数与采集中断分开

下面是完整的 rate_window.h。新窗口用零初始化;continuous=false 表示计数器被复位、采集暂停或其它已知的不连续情况。两次有效调用间隔超过 5000ms,也丢弃旧窗口并建立新基线。5000ms 是这份模块为约一秒更新设置的接口约束,不是盖革管物理参数。

/* Original statistics module, version 1.0.0, MIT License. */
#ifndef RATE_WINDOW_H
#define RATE_WINDOW_H
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <math.h>

enum { RATE_BASELINE, RATE_VALID, RATE_GAP, RATE_BAD_TIME };
typedef struct {
    uint32_t last_ms, last_count, dt[10], dn[10];
    uint64_t elapsed_ms, pulses;
    unsigned pos, used;
    bool baseline;
} RateWindow;

static inline void rate_reset(RateWindow *w, uint32_t ms, uint32_t count) {
    memset(w, 0, sizeof(*w));
    w->last_ms = ms; w->last_count = count; w->baseline = true;
}

/* continuous=false also signals counter reset or acquisition suspension. */
static inline int rate_push(RateWindow *w, uint32_t ms, uint32_t count,
                            bool continuous) {
    if (!w->baseline || !continuous) {
        rate_reset(w, ms, count);
        return RATE_BASELINE;
    }
    uint32_t dt = ms - w->last_ms;
    uint32_t dn = count - w->last_count;
    if (dt == 0) return RATE_BAD_TIME;
    if (dt > 5000) {
        rate_reset(w, ms, count);
        return RATE_GAP;
    }
    unsigned p = w->pos;
    w->elapsed_ms -= w->dt[p]; w->pulses -= w->dn[p];
    w->dt[p] = dt; w->dn[p] = dn;
    w->elapsed_ms += dt; w->pulses += dn;
    w->last_ms = ms; w->last_count = count;
    w->pos = (p + 1) % 10;
    if (w->used < 10) ++w->used;
    return RATE_VALID;
}

/* Call only after RATE_VALID; n=0 has an upper bound, not relative error. */
static inline double rate_cpm(const RateWindow *w) {
    return w->elapsed_ms ? 60000.0 * w->pulses / w->elapsed_ms : NAN;
}
static inline double rate_rel_std(const RateWindow *w) {
    return w->pulses ? 1.0 / sqrt((double)w->pulses) : NAN;
}
static inline double rate_zero_upper95(const RateWindow *w) {
    return w->elapsed_ms && !w->pulses
        ? -log(0.05) * 60000.0 / w->elapsed_ms : NAN;
}
#endif

调用返回 RATE_VALID 才更新有效统计显示。RATE_BASELINE、RATE_GAP 显示“建立基线/采集间断”,不能拿零计数率冒充有效结果;RATE_BAD_TIME 表示重复时间戳,本次不消费计数,下次有效快照继续计算。

传入的时间和累计计数必须来自同一次一致快照。GD32 的计数变量由中断更新时,应声明为 volatile,并在极短临界区读出计数和毫秒值,退出时恢复进入前的中断屏蔽状态。毫秒基准不能随屏幕刷新暂停;若采集实际停止,必须传入不连续标志。临界区只负责取值,不在其中绘图或求平方根。

无符号减法可处理一次 32 位回绕,前提是相邻采样之间没有跨越一个完整计数周期;主动复位不能伪装成回绕。模块内部累加用 64 位,避免十段相加溢出。这里实现了统计模块,并未声称整套 GD32 显示固件已经完成目标编译或实机验证。

用三个可复现输出检查接口

把上面的头文件与下面的 demo.c 放在同一目录。采用 Zig 0.13.0 随附 C 编译器,运行 zig cc -std=c11 -Wall -Wextra -Werror demo.c -o demo.exe,再执行 demo.exe。标准 C 编译器也可使用;部分 Unix 工具链需在链接参数末尾加 -lm。这段程序不需要连接硬件。

#include <stdio.h>
#include "rate_window.h"

int main(void) {
    RateWindow w = {0};
    rate_push(&w, 0, 0, true);
    rate_push(&w, 2000, 3, true);
    printf("3 pulses / 2s = %.3f CPM\n", rate_cpm(&w));
    rate_reset(&w, 0, 0);
    for (unsigned i = 1; i <= 10; ++i)
        rate_push(&w, i * 1000, 0, true);
    printf("0 pulses / 10s: upper95 = %.3f CPM\n",
           rate_zero_upper95(&w));
    int status = rate_push(&w, 16000, 0, true);
    printf("gap status = %d, valid intervals = %u\n", status, w.used);
    return 0;
}

本地实际输出为:

3 pulses / 2s = 90.000 CPM
0 pulses / 10s: upper95 = 17.974 CPM
gap status = 2, valid intervals = 0

另用 25 项断言核对了启动、两秒分母、重复时间戳、环形窗口替换、计时抖动、零计数上限、超过五秒间断、主动复位、毫秒与计数同时回绕,以及超过 32 位的窗口累加。测试输出为 PASS 25 checks; zero10s_upper95=17.974393641 CPM。这些是软件测试输入,不能当作盖革管实测或校准数据。

图纸核对也有自己的范围

盖革管图页整体电路

图 4 V1.0.0 的盖革管电路整体。左侧是升压部分,右侧是脉冲输出;统计模块只消费已经成形的低压计数。带电高压端不能直接接 MCU 或普通逻辑分析仪,这篇文章不提供改动高压输出的操作步骤。

PCB顶面

图 5 同一 EDA 工程的 PCB 仅顶面导出,保留原版布局与丝印。母图为 3903×4096px。

PCB底面

图 6 对应 PCB 仅底面导出,按 EDA 顶视观察方向显示,因此底面丝印呈镜像;不要把它当作翻板后的实物照片来数接口。

到实际设备上验证时,应先在断开高压部分的低压测试条件下检查 PE6 输入脉冲与累计计数,再检查显示刷新期间累计值是否连续。没有标准源和标定条件,就把输出保留为计数率与统计状态。让界面显示“观察了多久、收到了几次、这一窗是否连续”,比给不充分的计数补上四位小数更有用。

资料与许可

工程与参考软件入口为立创·梁山派盖革计数器,项目提供正文代码及软件工程入口。采用该项目的辐射检测扩展板 V1.0.0;工程图由嘉立创 EDA 4.1.100 在 2026-11-15 重新导出,局部裁剪、等比缩放,3D 图裁去编辑器界面。

工程及工程图署名嘉立创开发板团队、老怪鸽等项目成员,按项目标示的 CC BY-SA 4.0 使用;本文技术分析与工程图适配同样按该许可提供。新增统计代码为独立实现,按 MIT 许可提供。验证限于图纸对应关系、推导及本地 C11 编译/运行;没有辐射源实测、剂量标定或改版硬件测试。

创作许可协议

本项目采用 CC BY-SA(署名-相同方式共享) 进行许可。

评论(0)
- 没有更多了 -