1. 项目概述与核心思路
真随机数生成器(TRNG)在密码学、模拟仿真和博彩等领域至关重要,它区别于计算机生成的伪随机数,其随机性源于物理世界的不可预测现象。市面上很多DIY项目利用热噪声、大气噪声或放射性衰变,但一个常被忽视的痛点是:如何持续、便捷地验证这些“真随机数”的统计质量?很多项目生成数字后,用户需要手动导出数据到第三方软件(如Python的SciPy)进行卡方检验,过程繁琐且不实时。
这个项目的核心创新点在于“集成化”与“自动化”。它采用一个经典且可靠的物理熵源——晶体管反向偏置击穿产生的白噪声,并围绕Arduino Nano设计了一套完整的模拟前端电路。最关键的是,它在微控制器内部实现了实时的卡方拟合优度检验。生成一批随机数后,固件会自动进行统计检验,如果发现分布不均匀(例如,某些数字区间出现频率异常),它会立即丢弃这批数据,并自动重启采集流程,直到获得一批通过检验的“优质”随机数为止。这相当于为你的随机数源配备了一位24小时在线的质量检测员,确保了输出数据的长期统计可靠性。
我最初被这个设计吸引,是因为它完美解决了我在另一个量子噪声源项目中遇到的困扰:我无法实时知道设备是否在正常工作,数据是否“够随机”。搭建并深度改造这个项目后,我发现,它的价值远不止一个可用的随机数发生器,更是一个绝佳的、理解从模拟物理现象到数字统计验证全链条的嵌入式系统教学案例。无论你是电子爱好者想深入理解噪声利用和运放电路,还是软件开发者想学习如何在资源受限的MCU上实现统计检验算法,这个项目都能提供丰富的实践素材。
2. 硬件电路深度解析与关键设计
整个系统的硬件部分可以清晰地分为两大模块:噪声生成与调理模拟电路,以及数字采集与控制核心。理解每个元件的角色和设计背后的“为什么”,是成功复现和调试的关键。
2.1 熵源核心:晶体管噪声发生电路
项目的物理随机性源于一个巧妙利用晶体管特性的电路。与常见的利用晶体管热噪声(非常微弱,需极高增益放大,易受干扰)的方案不同,这里使用了更强劲的噪声源:晶体管发射结的反向击穿噪声。
核心原理:当NPN晶体管的发射结(基极-发射极)被施加一个高于其反向击穿电压(通常为5-9V)的反向偏置电压时,该PN结会进入齐纳击穿或雪崩击穿状态。在这个状态下,载流子的倍增过程会产生大量宽频谱、高幅度的白噪声。这比单纯放大微弱的基极-发射极热噪声要有效和稳定得多。
电路详解(对照原理图):
- Q1(噪声源):关键所在。其发射极(E)接高电压(经后续电路处理后的约15V),基极(B)通过电阻R1(例如100kΩ)接地,集电极(C)悬空或接一个较小的电阻到地。这种连接使得BE结反向偏置。型号选择上,BC548C、2N2222、2N3904等通用NPN管均可,但个体差异较大,这正是需要调试的原因。
- Q2(恒流源):它的作用是为Q1的击穿状态提供一个相对稳定的电流,使噪声输出幅度更平稳。Q2、R2和R3构成了一个简单的恒流源电路,电流值大致为
(Vcc - Vbe) / R2。Vbe约为0.6V,若Vcc为15V,R2为10kΩ,则电流约1.44mA。这个电流流经Q1的BE结,使其稳定工作在击穿区。 - 电源升压:Arduino Nano的5V输出不足以击穿BE结。因此项目使用了MT3608这类小型DC-DC升压模块,将5V升至约15V,为噪声发生电路供电。这里有一个重要注意事项:开关稳压器本身会产生高频纹波噪声(MT3608典型频率为1.2MHz)。虽然原理图作者表示在原型中未观察到干扰,但为了追求极致性能,建议采取隔离措施:在升压模块的输入和输出端并联大容量(如100μF)电解电容和小容量(如0.1μF)陶瓷电容进行滤波;尽量让升压模块远离模拟放大电路,或使用屏蔽罩。
2.2 信号调理:三级运放放大与电平移位
从Q1集电极取出的噪声信号幅度可能只有几十到几百毫伏,且是交流信号,中心在0V上下波动。我们需要将其放大并抬升到0-3.3V(Arduino ADC的输入范围)的单极性信号。
电路采用一颗四运放芯片(如TL074)完成所有调理:
- U1B(电压跟随器/缓冲器):第一级。直接从Q1采集信号。其高输入阻抗避免了从高阻抗噪声源汲取电流而影响信号,低输出阻抗则能驱动后续电路。这是信号链的起点。
- U1C(同相放大器)与 RV1(偏置调节):这是整个电路调试中最关键的一环。U1C被配置为同相放大器,其放大倍数由反馈电阻决定。RV1是一个多圈精密电位器(如10圈10kΩ),连接在运放的同相输入端。调节RV1,实质上是精细调节运放的直流工作点(偏置电压)。这个偏置电压会与噪声信号一起被放大。
- 调试目标:通过示波器观察U1C输出,目标是得到一个底部紧贴0V(或略高于0V约0.25V),峰值不超过3V的纯净噪声信号。底部贴0V是为了充分利用ADC的动态范围;不超过3V是为了绝对保护Arduino的ADC输入(极限3.3V)。
- 调试步骤:
- 先不接Arduino,仅给模拟电路上电。
- 将RV1逆时针旋到底(动片接地),此时偏置电压最低。
- 用示波器探头连接U1C输出端。
- 缓慢顺时针旋转RV1,观察示波器。你会先看到一条直线,然后开始出现噪声波形。继续微调,直到噪声波形的“谷底”刚好在0V线上方一点点,同时“峰顶”不超过2.5V-3V。记录下此时波形稳定的状态。
- U1D(可选的第二级放大器/缓冲器):根据Q1的个体差异,U1C放大后的信号幅度可能足够(如达到2Vpp),也可能不足。如果不足,则配置U1D为第二级同相放大器,进一步放大。如果U1C输出已足够,则可以将U1D配置为电压跟随器(将输出直接接回反相输入端,R8用0Ω电阻短接,R9不焊),仅起缓冲作用。这需要根据实测情况决定。
- U1A(直流偏移加法器):这是一个精妙的设计,用于消除对负电源的需求。经过C1隔直电容后,U1D输出的信号是中心为0V的交流噪声。但Arduino的ADC只能测量0-Vcc(通常5V)的正电压。U1A作为一个加法器,将噪声信号与一个约7.5V(即15V的一半)的直流电压相加。这样,原来在-1V到+1V之间波动的信号,就被抬升到了6.5V到8.5V之间。然而,请注意,后续通过电阻分压网络(R10, R11),这个电压又被衰减到0-3.3V的范围,同时完成了电平移位。最终送到Arduino A0引脚的,是一个在0V至约3V之间波动的单极性噪声信号。
重要心得:焊接完成后,务必先独立调试模拟电路,用示波器确认输出波形正确、幅度安全后,再连接Arduino。贸然连接可能导致ADC引脚过压损坏。调试时,耐心旋转RV1,有时需要来回微调才能找到那个既能让噪声充分显现,又不会引入失真或直流分量过大的“甜点”。
2.3 数字核心:Arduino Nano的配置与安全
Arduino Nano V3是这个项目的“大脑”,负责ADC采样、随机数提取、统计检验和串口通信。
- ADC参考电压:建议使用
analogReference(EXTERNAL)并接入一个稳定的3.3V基准电压源(如REF3033)到AREF引脚。这比使用不稳定的内部5V基准或3.3V LDO输出能获得更好的采样线性度和精度,使随机数的分布更均匀。 - 输入保护:尽管电路设计已将输出限制在3V以内,仍建议在A0引脚与地之间反向并联一对硅二极管(如1N4148),并将A0引脚通过一个100-500Ω的电阻连接到电路输出端。这构成了一个经典的钳位保护电路,确保即使电路故障,A0引脚电压也被钳制在-0.7V到Vcc+0.7V的范围内。
- 电源去耦:在Nano的5V和GND引脚附近,焊接一个10μF钽电容和一个0.1μF陶瓷电容,以滤除来自数字电路和升压模块的电源噪声,为ADC创造一个相对干净的环境。
3. 固件逻辑与核心算法实现
项目的固件(Sketch)是智能所在。它不仅仅是读取ADC值,更实现了一套完整的随机数生成、检验和管理的状态机。代码被模块化分成了多个.ino文件,结构清晰。
3.1 随机数生成策略:为何是“随机时刻采样”
这是理解本项目真随机性的关键。一个常见的误解是:直接快速连续读取ADC值就是真随机数。错!如果以固定频率(如每秒10000次)采样一个模拟噪声信号,得到的序列虽然值在变化,但相邻样本间可能存在相关性(由噪声的有限带宽导致),且其幅值分布是噪声信号的概率密度函数(通常是高斯分布),而非我们想要的均匀分布。
本项目的策略堪称点睛之笔:
- 熵源:模拟引脚A0上的电压,是一个连续的、不可预测的模拟噪声信号(物理熵)。
- 采样时机:使用Arduino内置的
random()函数(伪随机数生成器)来产生一个随机的“等待时间”。例如,delayMicroseconds(random(100, 5000));这意味着在两次采样之间,程序会随机等待100到5000微秒。 - 采样动作:在随机等待结束后,立即执行一次
analogRead(A0),读取此刻的噪声电压值(0-1023)。 - 均匀化(可选):直接得到的0-1023的数值,其分布可能因电路偏置而不完全均匀。可以通过取模运算(如
randNum % 100)将其映射到0-99的均匀整数区间,或者使用更复杂的方法如拒绝采样。
为什么这样有效?伪随机数生成器random()用于控制采样时刻,它虽然是确定性的,但其周期极长,在人类尺度上用于触发采样事件可以视为“随机”的。而采样的值来源于那个时刻的物理噪声,这个值是纯粹物理的、不可预测的。这就将伪随机数生成器的长周期特性与物理熵源的不可预测性结合了起来,既保证了采样时刻的“无规律”,又保证了采样值的“真随机”。简言之,random()决定“何时看”,物理噪声决定“看到什么”。
3.2 集成化卡方检验的实现
卡方拟合优度检验是项目的核心自检功能。其目的是判断一批随机数是否服从均匀分布。
算法步骤(在MCU中实现):
- 设定参数:假设我们生成了
n = 300个随机数,每个数的范围是[0, k-1],例如k=100(0-99)。我们将这个范围均匀划分为r个区间(称为“箱子”),比如r = 10,那么每个箱子对应一个数字子集:0-9, 10-19, ..., 90-99。 - 统计观测频数:遍历这300个数,统计落在每个箱子里的实际数量,记为
O_i(i=1 to r)。 - 计算期望频数:在完全均匀分布的理想情况下,每个箱子预期的数字个数是
E = n / r = 300 / 10 = 30。 - 计算卡方统计量:使用公式
χ² = Σ [ (O_i - E)² / E ],对每一个箱子进行计算并求和。 - 判断:根据卡方分布表,在显著性水平α(通常取0.05或0.01)和自由度
df = r - 1下,找到一个临界值。例如,df=9, α=0.01时,临界值约为21.67。如果计算出的χ²小于这个临界值,我们认为这批数据“通过”了均匀性检验;反之,则拒绝。
在固件中的实现:
- 函数
performChiSquareTest()实现了上述逻辑。 - 它预先定义了一个“临界值”(如对应1%显著水平的21.67)。
- 在
gi(智能获取)命令中,固件循环执行:生成数组 -> 卡方检验。如果检验失败,则自动重新生成,最多重试10次。如果10次都失败,则返回最后一次的数据并提示警告。 - 可调参数:箱子的数量
r、显著性水平α对应的临界值,都可以在代码开头定义为常量,方便用户根据不同的应用场景调整检验的严格程度。
3.3 串口命令处理器与工作流程
项目使用了MD_cmdProcessor库来构建一个简洁的串口命令行界面,这使得交互和调试非常方便。
主要命令解析:
gr:获取单个随机数。立即采样一次A0并返回。用于快速测试电路是否工作。ga [数量]:获取指定数量的随机数数组。使用上述“随机时刻采样”策略填充数组,然后直接返回数组内容。不进行卡方检验。gi [数量]:智能获取。这是核心命令。它会生成数组,并自动进行卡方检验。如果检验失败,则重试,直到成功或达到最大重试次数。返回最终通过检验的数组。这确保了输出数据的统计质量。st:统计。对当前内存中的随机数数组进行分析,计算并打印出每个区间(箱子)的实际频数、期望频数以及卡方值。用于手动分析。sp:绘图。将st命令的结果以一种适合Arduino串口绘图器(Serial Plotter)的格式输出,可以直观地看到频数分布的条形图。ab:自动偏置测量。这是一个非常重要的安全与校准命令。它会连续读取A0引脚约15秒,计算其平均值(即直流偏移)和峰值波动。用户应在连接Arduino但不运行ga/gi命令时执行此命令,以确认A0引脚上的电压偏移是否在安全范围内(例如,平均值在1.0V-2.0V之间,峰值不超过3.0V)。如果偏移太大或太小,需要返回去调整硬件上的RV1电位器。
工作流程示例: 用户通过串口监视器发送gi 300。
- Arduino接收到命令,进入
handlerGI函数。 - 调用
generateRandomArray(300)函数,用随机延迟采样法填充一个300元素的数组。 - 调用
performChiSquareTest()函数检验该数组。 - 若
χ² < 临界值,检验通过,通过串口输出这300个数字。 - 若检验失败,计数器加1,返回步骤2重试。
- 若重试超过10次仍失败,则输出最后一次生成的数据,并提示“警告:未能在重试次数内通过卡方检验”。
- 整个过程中,
ab命令是硬件校准的辅助工具,st和sp是事后分析工具。
4. 构建、调试与问题排查实录
4.1 分步构建指南
物料准备:
- 核心IC:TL074CN(或TL084)四运放 x1, MT3608升压模块 x1, Arduino Nano x1。
- 晶体管:NPN如2N2222或BC548C x2。
- 电阻: 10kΩ, 100kΩ, 1MΩ等(按原理图),精度5%即可。
- 电容: 10μF电解, 0.1μF, 100pF陶瓷等。
- 电位器: 10kΩ多圈精密电位器 x1。
- 其他:面包板或PCB,连接线,示波器(强烈推荐),万用表。
焊接与组装:
- 建议先在面包板上搭建模拟电路部分(不包括Arduino)。这便于调试和修改。
- 电源走线尽量粗短,地线形成星型接法或单点接地,减少噪声耦合。
- 模拟部分(运放电路)和数字部分(Arduino、升压模块)在布局上尽量分开。
上电前检查:
- 再三核对原理图,特别是晶体管、运放和电解电容的引脚方向。
- 用万用表通断档检查电源和地之间有无短路。
模拟电路独立调试(无Arduino):
- 仅给模拟部分接通5V输入(升压模块输出15V给运放)。
- 将RV1逆时针旋到底。
- 示波器探头接在U1C的输出端(或U1D输出端,如果你用了第二级)。
- 上电,缓慢顺时针旋转RV1。你的目标是:在屏幕上看到一条“毛茸茸”的带子(噪声),其底部(最低电压)在0V线上方一点点(0.1V-0.3V),顶部(最高电压)不超过2.5V-3V。调整时要有耐心,可能需要非常细微的旋转。
- 如果旋转RV1过程中,输出突然变成一条稳定的高电平或低电平直线(运放饱和),或者噪声幅度始终很小,请检查晶体管Q1是否已进入击穿状态(测量其BE结电压是否在6-9V之间),以及各级运放的连接和供电。
连接Arduino与软件调试:
- 确认模拟输出正常后,关电,将输出点通过一个100Ω电阻连接到Arduino Nano的A0引脚。
- 将Arduino通过USB连接到电脑,上传完整的项目固件。
- 打开串口监视器(波特率9600)。发送
ab命令。观察输出的平均电压和峰值。确保平均电压在1-2V之间,峰值电压小于3.3V。如果不符合,关电,重新调整RV1,重复步骤4。 - 校准完成后,发送
gr命令。你应该能看到一个快速变化的0-1023之间的数字。多次发送gr,观察数字是否看起来无规律。 - 发送
ga 50获取50个数字,观察输出。再发送st查看这50个数的初步分布。 - 最后,发送
gi 100。系统会生成100个随机数并自动进行卡方检验。在串口输出中,你不仅能看到数字列表,还能看到“Chi-square test passed”或失败重试的信息。
4.2 常见问题与解决方案速查表
| 问题现象 | 可能原因 | 排查与解决步骤 |
|---|---|---|
| 示波器无噪声信号,只有直流电平 | 1. RV1未调好,运放饱和。 2. 晶体管Q1未击穿。 3. 电源未接通或电压不对。 4. 运放芯片损坏或引脚接错。 | 1. 将RV1回调至接地端,重新缓慢调节。 2. 测量Q1的BE结电压,应有6-9V反向电压。若无,检查R1、Q2恒流源及15V供电。 3. 用万用表测量运放电源引脚(第4脚Vcc+, 第11脚Vcc-应为负或接地,取决于单电源接法)。 4. 对照数据手册检查TL074引脚连接。 |
| 噪声信号幅度太小(< 0.5Vpp) | 1. Q1个体噪声输出弱。 2. 运放增益不足。 3. RV1偏置点不合适。 | 1. 尝试更换另一个型号或批次的NPN晶体管(如换用2N2222)。 2. 检查U1C和U1D的反馈电阻,确保放大倍数足够(例如,每级10倍)。 3. 微调RV1,寻找最佳工作点。 |
| 噪声信号顶部或底部被削平(失真) | 输出幅度过大,运放输出达到电源轨(轨至轨运放接近Vcc或GND)。 | 立即调小RV1,降低偏置,防止损坏后续ADC。确保峰峰值在3V以下。 |
ab命令显示A0电压接近0V或5V | 1. 模拟电路未工作或输出为固定电平。 2. 连接线断路。 3. A0引脚已在代码中被设置为输出模式。 | 1. 返回模拟电路调试步骤。 2. 检查A0引脚的连接。 3. 检查代码,确保 setup()中未设置pinMode(A0, OUTPUT)。 |
ab命令显示A0电压持续高于3.3V | 硬件输出偏移或幅度过大,极其危险! | 立即断开Arduino与模拟电路的连接!重新在示波器上调整RV1,确保输出底部>0V且顶部<3V。 |
gi命令总是失败(卡方检验不通过) | 1. 随机数质量差(电路噪声小或有规律干扰)。 2. 卡方检验参数过于严格。 3. 采样策略问题。 | 1. 用示波器FFT功能观察噪声频谱,检查是否有明显的单一频率干扰(如来自升压模块)。加强电源滤波和物理隔离。 2. 在代码中适当调高卡方检验的临界值(放宽标准),或减少“箱子”的数量(r)。 3. 检查 generateRandomArray函数中的随机延迟范围random(min, max),确保范围足够大(如至少几十微秒到几毫秒),避免周期性。 |
| 随机数序列看起来有规律 | 1. 物理熵源不足。 2. 伪随机种子固定。 | 1. 确保噪声信号在示波器上看起来“活跃”且无规律。尝试改善电路。 2. 在 setup()中,使用一个未连接的模拟引脚(如A7)的浮动值作为randomSeed()的种子,增加初始随机性:randomSeed(analogRead(7));。 |
| 串口无响应或乱码 | 1. 波特率设置错误。 2. USB线或串口驱动问题。 3. 代码未正确上传。 | 1. 确认串口监视器波特率设置为9600,与代码中Serial.begin(9600)一致。2. 尝试不同的USB口,重启Arduino IDE。 3. 重新编译并上传代码。 |
4.3 性能优化与扩展思路
在基本项目成功运行后,你可以考虑以下优化和扩展:
- 熵源质量评估:编写一个简单的“熵估计”函数。连续采集大量样本,计算它们的字节级或比特级自相关性,或者计算香农熵。这比卡方检验更能反映随机性的“不可预测性”。
- 后处理:对于要求极高的应用,可以对原始ADC采样值进行后处理,如使用冯·诺依曼校正法(Von Neumann extractor)来消除微弱的偏置,或者使用哈希函数(如SHA-256)对原始熵进行提取和混合。注意,在Arduino Nano上运行复杂哈希函数可能较慢。
- 输出速率优化:当前的“随机延迟采样”策略速度较慢。可以尝试一种混合模式:快速连续采样一小段噪声波形(如1000个点)存入缓冲区,然后用一个伪随机数生成器(以某个物理熵值作为种子)从缓冲区中随机选取位置读取数值。这样能在保证随机性的前提下提高输出速率。
- 无线控制与数据流:添加一个HC-05或ESP-01S WiFi模块,将设备升级为无线随机数服务器。你可以通过手机APP或网络请求来获取随机数。
- 应用扩展:正如原作者提到的,可以修改固件,制作一个“电子骰子”。将输出范围映射到1-6、1-20等,并搭配LED或显示屏,就是一个有趣的桌面游戏工具。更进一步,可以将其作为密码学应用的本地熵源。
这个项目的魅力在于,它从一个具体的物理现象出发,穿越了模拟电路设计、嵌入式编程和数理统计,最终交付了一个可靠、自检的实用化工具。调试过程中,示波器上那一片舞动的噪声最终化为串口终端里均匀分布的数字,这种将物理世界的混沌转化为信息世界确定性的过程,充满了工程师所独有的乐趣。