基于放射性衰变的真随机数生成器:从量子物理到嵌入式实现
1. 项目概述一个基于物理熵源的“真”随机数生成器作为一名电子爱好者我总喜欢把一些看似不相干的技术点子揉在一起做出点既有趣又有实际用途的东西。这次的项目就是把我最钟爱的三个领域——时钟、晶体振荡器和电离辐射——给攒到了一块儿捣鼓出了一个“真随机数生成器”True Random Number Generator, TRNG。这玩意儿不仅是个能精确走时的时钟更核心的功能是它能利用自然界最不可预测的事件之一——放射性衰变——来产生无法被算法预测或复现的随机数。在很多场景下比如普通的模拟、游戏或者一些对安全性要求不高的场合用软件算法生成的“伪随机数”就够用了。但伪随机数本质上是一个确定性的、周期很长的数列只要知道算法和种子理论上就能预测或重现整个序列。这在密码学、安全密钥生成、高精度科学模拟以及博彩等领域是绝对不允许的。真正的随机性必须源于一个物理世界的、本质上不可预测的过程。我这个项目就是用一枚上世纪60年代的盖革管去捕捉来自一块古董镭表盘发出的辐射粒子用这些完全随机的“天外来客”来冻结一个高速运行的计数器从而得到一个货真价实的随机数。这个设备最让我得意的地方在于它的“混搭”美学核心计时用的是一颗1950年以前的军用发射机晶体驱动它的是一个1971年生产的、早已停产45年的DTL逻辑门电路。而随机数的熵源则是一块已经不走字、但辐射依旧的镭涂层手表。整个系统既有历史的厚重感又精准地完成了现代电子工程的任务——时钟月误差控制在3-4秒随机数生成则依赖于宇宙级的随机性。去年它放在我办公室外面成了同事们每天必来的“打卡点”就为了获取他们专属的“每日幸运随机数”。2. 核心设计思路与方案选型2.1 为什么选择放射性衰变作为熵源构建一个TRNG核心在于找到一个稳定、高速且本质上不可预测的物理熵源。常见的方案有基于半导体噪声如热噪声、散粒噪声、基于混沌电路、基于大气噪声以及基于放射性衰变。我选择放射性衰变主要基于以下几点考量理论上的绝对随机性放射性原子核的衰变是一个纯粹的量子力学过程。对于一个特定的原子核其衰变时刻是完全无法预测的只服从统计规律即半衰期。这意味着即使你知道所有初始条件也无法计算出下一个衰变何时发生。这种源于物理定律本身的不可预测性是最高等级的随机性。信号干净易于检测单个放射性粒子如α或β粒子与盖革管内的气体分子碰撞会产生一个明显的、陡峭的电脉冲信号。这个信号幅度大通常几百毫伏到几伏、边沿陡非常容易被数字电路如MCU的中断引脚可靠地捕获几乎无需复杂的模拟信号调理电路。熵产生速率稳定可控通过选择不同活度的放射源可以调节随机事件发生的平均速率。我使用的镭表盘每秒能产生约2个事件这个速率对于生成随机数来说既不会太慢导致等待时间过长也不会太快超过MCU的处理能力是一个很理想的“甜点”。背景辐射的普遍性即使没有人工放射源自然界也存在本底辐射来自宇宙射线、土壤中的放射性元素等。这意味着理论上在任何地方这个设备都能工作只是速率较慢。加入一个弱放射源相当于给熵源“加了把劲”保证了设备响应的实时性。相比之下基于电路噪声的方案需要极其精密的模拟前端来放大微弱的噪声信号并要解决电源噪声、环境干扰等问题设计门槛较高。而放射性方案一旦有了可靠的盖革管和高压电源后续的数字处理部分就非常直接和鲁棒。2.2 系统架构与工作流程整个设备是一个典型的嵌入式系统其核心工作流程围绕着“事件驱动”展开。下图清晰地展示了从熵源到最终随机数显示的完整数据流与控制逻辑flowchart TD A[物理熵源br古董镭表盘] --|持续发射辐射粒子| B[辐射探测器br5980型盖革管] B --|产生负脉冲| C[信号调理电路br555单稳态触发器] C --|输出3.3V逻辑脉冲| D[MCU中断引脚] D -- E{MCU核心处理流程} E -- F[启动48MHz计数器] F -- G[等待辐射中断] G --|中断发生| H[立即冻结计数器] H -- I[读取计数器值作为随机数种子] I -- J[应用后处理算法br如Von Neumann校正] J -- K[显示随机数] L[时间基准br2145 KC军用晶体] --|经DTL振荡器| M[MCU定时器] M -- N[驱动实时时钟与br计数器时钟源] subgraph “用户交互” O[红色按钮] --|按下| P[触发随机数br生成与显示流程] end P -- E N -- F这个架构的关键在于速度与确定性的分离。高速运行的计数器48MHz提供了精细的时间分辨率而完全不可预测的辐射事件则提供了“采样”这个时间轴的随机时刻。两者结合确保了输出值的不可预测性。时间基准电路独立工作保证了时钟功能的精确性同时也为计数器提供了稳定的频率源。3. 硬件设计与关键模块解析3.1 “古董级”时间基准晶体振荡器电路时钟的精度是整个系统的基石也是我个人情怀的体现。我没有使用现成的温补晶振或MCU内部振荡器而是选择了一套充满历史感的方案。核心元件FT-171-B晶体这是一颗来自二战后期BC-610军用无线电发射机的晶体标称频率2145 kHz即2.145 MHz。其特点是采用大型金属外壳和香蕉插头物理结构坚固频率稳定性在当年属于顶级。经过大半个世纪其性能依然出色。振荡电路MC857 DTL四与非门DTL二极管-晶体管逻辑是TTL家族的前身在1960年代盛行。我用其中两个门搭建了一个经典的皮尔斯振荡器电路。第一个门与晶体、电容构成振荡回路第二个门用于缓冲和整形输出较为规整的方波。电平转换2N1304晶体管钳位MC857工作在5V而我的主控MSP432是3.3V系统。直接连接可能损坏MCU引脚。这里巧妙地利用了一个PNP晶体管2N1304的基极-发射极结作为钳位二极管。当振荡器输出高电平时约5V基极-发射极正向导通将电压钳位在约0.7V相对于5V实际上在发射极输出的是约4.3V。但通过合适的分压或直接利用其特性最终能安全地送入3.3V的MCU时钟输入引脚。这是一种经典、简洁的模拟电平转换技巧。实操心得古董元件的“驯服”使用老元件最大的挑战不是性能而是知其所以然。MC857的数据手册早已难寻其驱动能力、翻转速度都需要通过实验验证。搭建振荡器时反馈电阻和电容的值需要根据晶体特性微调。我用示波器仔细观测了起振时间和波形稳定性确保在电源波动和环境温度变化下仍能可靠工作。最终月误差3-4秒的成绩证明了这套“老家伙”组合的实力。3.2 随机数熵源盖革管与高压电源这是TRNG功能的核心硬件负责将看不见的辐射转化为电信号。探测器5980型盖革-米勒管这是一种卤素淬灭型GM管工作电压通常在400-600V。我将其密封在一个铝制外壳中一方面屏蔽电磁干扰另一方面也防止意外触碰高压部分。铝壳对β和γ射线是透明的不影响探测。高压生成电路Flyback 倍压这是整个项目中最需要谨慎对待的部分。我采用了一个基于555定时器的自激振荡电路驱动MOSFET配合一个初级电感构成反激式开关电源。MOSFET快速开关在电感次级感应出高压脉冲再经过一个由高压二极管和电容组成的倍压电路最终输出约650V的直流高压供盖革管使用。关键参数计算高压值由555的振荡频率、占空比、电感量以及倍压电路共同决定。粗略估算假设输入18V反激拓扑的理想升压比与占空比D有关V_out ≈ V_in * (D/(1-D)) * NN为匝比再经过倍压电路近似翻倍。需要通过实际测量并调整电位器来精确设定到盖革管的最佳工作电压即“坪区”。脉冲调理电路盖革管输出一个负向的、幅度与高压相关的窄脉冲。这个脉冲不能直接接入MCU。我的设计是先用一个2N3904 NPN晶体管进行缓冲和反相变为正脉冲然后用第二个555搭建成一个单稳态触发器。任何来自盖革管的脉冲都会触发这个555产生一个宽度固定例如100微秒的、干净的3.3V逻辑电平脉冲。这个脉冲直接送入MSP432的外部中断引脚。一个巧妙的设计在旧的手绘原理图中555单稳态的输出通过一个蓝色LED的阳极引出。LED本身的正向压降约2.8-3.2V恰好起到了一个简单的限幅作用使得输出高电平不会超过3.3V太多完美匹配MCU的GPIO电压无需额外的电平转换芯片。这是硬件设计中“一石二鸟”的典范。安全警告高压危险650V直流电压足以造成严重电击。整个高压部分必须被充分绝缘和物理隔离。我在调试时总是先断开与盖革管的连接用高压探头在示波器上测量输出确认电压稳定且电路无异响、发热后再连接负载。铝制外壳必须可靠接地接电源地。强烈建议不具备高压经验的朋友使用现成的、封闭式的盖革管模块。3.3 主控与显示单元主控MCUTI MSP-EXP432P401R LaunchPad选择这款开发板是因为其MSP432P401R MCU具有低功耗、高性能Cortex-M4F内核的特点并且自带丰富的定时器资源。我使用了其中一个32位通用定时器Timer_A或Timer_B配置为连续向上计数模式时钟源选择系统主时钟经过PLL倍频到48MHz。当中断来临时立即读取定时器计数寄存器TAxR的值。图形LCD显示采用并行接口的图形LCD可以灵活显示时间、随机数、计数率等信息。驱动这类屏幕需要仔细配置GPIO的时序通常需要微秒级的延时来满足建立和保持时间。我将显示刷新放在主循环中而随机数生成和时钟更新则在中断服务程序ISR中处理标志位以避免在ISR中进行耗时操作。4. 软件实现与随机数生成算法4.1 固件主流程与中断处理软件的核心是高效、正确地响应随机事件并管理时钟。// 伪代码示意核心逻辑 volatile uint32_t random_seed 0; volatile bool new_random_ready false; void main(void) { // 初始化 init_clock_48MHz(); // 设置系统时钟包括PLL init_timer_for_counter(); // 将Timer_A配置为连续计数模式时钟SMCLK (48MHz) init_geiger_interrupt(); // 配置连接Geiger脉冲的GPIO为下降沿触发中断 init_LCD(); // 初始化显示屏 init_RTC_from_crystal(); // 用外部2.145MHz信号校准或驱动RTC __enable_interrupt(); // 全局开启中断 while(1) { update_display_time(); // 更新时钟显示 if(new_random_ready) { display_random_number(random_seed 0xFFFF); // 显示低16位 new_random_ready false; } if(red_button_pressed()) { // 检测红色按钮 start_random_generation_sequence(); // 启动一次随机数生成流程 } enter_low_power_mode(); // 进入低功耗模式等待中断唤醒 } } // Geiger脉冲中断服务程序 #pragma vectorPORT1_VECTOR __interrupt void Geiger_ISR(void) { static bool capturing false; if(capturing) { random_seed TIMER_A-R; // 立即捕获计数器值 TIMER_A-CTL ~MC__CONTINUOUS; // 可选停止计数器 new_random_ready true; capturing false; } // 清除中断标志 P1-IFG ~BIT2; } // 红色按钮处理简化 void start_random_generation_sequence(void) { TIMER_A-R 0; // 计数器清零 TIMER_A-CTL | MC__CONTINUOUS; // 启动计数器连续计数 capturing true; // 允许ISR捕获下一个事件 // 显示“等待中...”提示 }关键点解析中断响应速度从辐射脉冲触发中断到ISR中读取计数器值这之间的延迟中断延迟必须尽可能短且稳定。MSP432的中断响应通常在十数个时钟周期内在48MHz下意味着亚微秒级的延迟。这对于随机数的质量影响微乎其微因为事件间隔在百毫秒量级。原子性操作random_seed是一个在中断和主循环中共享的变量它被声明为volatile以防止编译器优化。在32位架构上读取32位计数器值本身是原子的。更复杂的后处理如果需要应注意临界区保护。低功耗设计设备需要长期运行。在主循环中当没有显示更新或按钮处理时让MCU进入低功耗模式如LPM0由定时器中断用于时钟更新和外部中断盖革脉冲唤醒能极大降低功耗。4.2 从原始计数值到高质量随机数直接捕获的计数器值random_seed已经是随机的但可能存在微小的偏差需要进行后处理以提取出统计特性更完美的随机比特流。偏差来源分析非均匀分布由于辐射事件的平均间隔是固定的约0.5秒而计数器以48MHz运行这意味着计数器值大多会落在0到约24,000,00048M * 0.5这个范围内。虽然事件发生时刻是随机的但计数值的分布范围被限定了不会均匀覆盖整个32位空间0-4,294,967,295。低位随机性最低的几位比如低4位由于计数器高速运行即使事件间隔有微小抖动其值也会非常随机。但高位可能变化很慢。相关性连续两次捕获的值在时间上非常接近它们的差值可能很小存在短期相关性。后处理方法——Von Neumann校正器 这是一种简单有效的去除偏置的方法。它处理的是比特流而不是整个数字。方法将原始计数值的二进制位两两分组例如每次取16位分成8对。对于每一对比特如果是01则输出0。如果是10则输出1。如果是00或11则丢弃不输出。原理无论0和1的原始出现概率是多少只要不为0或1经过这样处理输出0和1的概率一定是相等的。因为01和10出现的概率在稳态下是相同的。缺点输出速率会降低因为丢弃了00和11的情况。在我的项目中由于熵源速率本身不高~2 Hz直接使用Von Neumann校正会使得随机比特产出率太低。因此我仅将其作为一个可选的软件后处理步骤对于要求不高的展示用途直接显示低16位已经足够“随机”。我的实际处理方案 为了在显示上获得快速且直观的随机数我采用了以下混合策略直接显示当用户按下红色按钮程序等待下一个辐射事件捕获计数器值后直接取其低16位或低4位十六进制数显示在LCD上。这提供了即时的、“够用”的随机性。熵池积累在后台我将每一个捕获的32位原始值通过一个哈希函数如简单的移位-异或组合累加到一个“熵池”中。当熵池积累到一定量后可以从中提取出更均匀分布的随机数用于更严苛的场合如果未来扩展功能。这类似于/dev/random的工作方式。5. 调试、优化与常见问题排查5.1 硬件调试实录晶体振荡器不起振现象示波器在晶体引脚上看不到正弦波或方波。排查首先确认电源5V稳定DTL芯片已上电。用示波器探头设置为10X档减少负载效应测量第一个与非门的输入和输出引脚。有时需要轻微触碰或用电容微调才能起振。检查反馈电阻通常跨接在门电路输入输出之间值在1MΩ到10MΩ之间是否焊接良好。尝试更换负载电容晶体两端对地的电容通常十几到几十皮法。我用的是可调电容慢慢旋转直到出现稳定的振荡波形。解决最终发现是其中一个22pF的负载电容虚焊。补焊后波形立即出现。盖革管无输出或计数率异常低现象LCD上显示的事件速率远低于预期例如几分钟一次或者完全为零。排查安全第一确认高压已关闭用放电器对高压输出端放电。测高压使用高压探头或万用表高压档测量供给盖革管的电压。调整555电路中的电位器确保电压在盖革管规格的坪区内对于5980管可能在550-750V之间。电压过低会导致不灵敏过高则可能连续放电损坏管子。测脉冲将辐射源镭表靠近盖革管用示波器探头测量单稳态555的输出引脚或LED阳极。应该能看到周期性的正脉冲。如果没有向后级检查测量盖革管输出端是否有负脉冲幅度可能几百伏需使用高压探头并极其小心。检查2N3904缓冲级是否工作基极电阻是否合适。检查第一个555振荡器是否工作为MOSFET提供驱动。检查接地与屏蔽确保整个高压部分的地与数字部分的地是单点连接的并且铝壳良好接地。电磁干扰可能淹没微弱的脉冲信号。解决我的案例中是高压倍压电路的一个高压二极管反向漏电流过大导致电压无法升至足够高。更换为耐压更高的二极管后问题解决。MCU中断无法触发现象高压和脉冲电路工作正常示波器能看到干净的3.3V脉冲但MCU就是不进入中断。排查检查GPIO配置是否正确设置为输入、上拉/下拉是否合适、中断边沿上升沿/下降沿是否与脉冲信号匹配。在GPIO引脚上接一个LED在中断服务程序里翻转LED状态这是最直接的调试方法。检查中断是否被全局禁用或者该中断向量是否被其他更高优先级中断阻塞。使用MCU的调试器设置断点在ISR入口观察是否能触发。解决发现是代码中初始化顺序问题先配置了GPIO中断但在后面初始化其他外设时意外改写了该GPIO所在端口的中断使能寄存器。调整初始化顺序后解决。5.2 随机数质量评估与优化如何知道生成的数字真的是“随机”的不能只靠感觉。简单测试频数测试连续生成大量数字比如10万个统计每个十六进制位0-F出现的次数。理论上应该大致均匀。我写了个简单的测试程序通过串口将数据发送到电脑用Python的collections.Counter进行统计结果偏差在统计学可接受范围内。游程测试检查连续相同比特或数字的长度分布。真正的随机序列中短游程居多长游程很少但也会出现。扑克测试将序列分成固定长度的组如5比特一组统计每种组合出现的频率。这能检测出更复杂的模式。我观察到的现象与优化初始现象直接使用原始计数器低16位发现数字在较小范围内出现的概率略高印证了之前的偏差分析。优化措施我引入了简单的“抖动”策略。在等待辐射事件时我让计数器从0开始计数但会在一个随机延迟由另一个低频噪声源或软件伪随机数生成后才真正使能捕获标志。这样即使事件间隔有微小相关性被采样的计数器值范围也被人为地“抖动”开了分布更均匀。这相当于在物理熵源的基础上加入了一点算法上的“搅拌”。6. 项目总结与扩展思考这个项目从构思到完成前后断断续续花了几个月的时间。最大的乐趣不在于最终那个显示着数字的盒子而在于过程中对各个子系统的钻研和调试让一颗70岁的晶体重新振动让一个50岁的集成电路再次工作理解并安全地驾驭高压电路最后用现代MCU将这一切统合起来实现一个兼具实用性与趣味性的功能。我个人在实际操作中的体会是跨时代的技术融合往往能带来意想不到的启发和稳健的设计。老元件在设计上往往留有充足的余量和清晰的物理逻辑而现代MCU则提供了无与伦比的灵活性和集成度。将两者结合你既能享受到经典电路的简洁可靠又能实现复杂的逻辑和控制。最后再分享一个小技巧对于这类长期运行的项目电源的稳定性至关重要。我使用的旧笔记本电源19V本身纹波较大所以我额外增加了一级LC滤波和线性稳压器从18V降到5V给模拟部分数字部分则直接由开发板的稳压器供电。良好的电源是系统稳定运行的无声基石。这个“盖革时钟随机数生成器”本身已经是一个完整的产品但它还有很大的扩展空间。例如可以增加Wi-Fi模块将生成的随机数流上传到服务器作为一个在线的随机熵源服务或者增加更多的传感器温度、气压将环境数据也作为熵源的一部分混合进来进一步增强随机性的不可预测性。它不仅仅是一个工具更是一个关于时间、随机性和电子工程历史的对话载体。每当有同事按下那个红色按钮等待一个来自原子核深处的随机礼物时我觉得这就是技术最迷人的地方。