首页 > 解决方案 > Arduino新手。“随机”值在重置时重复

问题描述

我正在 arduino 的基于 C++ 的环境中编写轮盘赌游戏,该游戏会闪烁 LED 以表示球盘旋和停止。我的问题是球每次都停在同一个位置。我正在使用 randomSeed(analogRead(0)) 结合 random(min,max) 来生成随机数。这个随机数代表我的球“进入”的位置。但是,当我重置我的 arduino 时,我得到了完全相同的结果。有谁知道我的问题可能出在哪里?

一些重要的注意事项

-我在全局范围内调用随机,因为我想要轮盘游戏的单个随机起点(“球”进入的位置)。当这两者一起工作时,设置中的 randomSeed 和全局范围中的 random 会导致问题吗?我不熟悉“设置”如何与 arduino 中的其余代码交互。

- 我生成的随机数在一个非常小的范围内(0-4)。但是,我已经多次重置我的 arduino,并且连续 30 次将 2 作为第一个数字。

我也尝试在全局范围内调用 randomSeed 而不是 setup 但这样做时会遇到以下错误。" 退出状态 1 预期的构造函数、析构函数或在 '(' 标记之前的类型转换"

如果我可以提供更多详细信息,或者如果有人知道类似问题的帖子,请告诉我。

Arduino 代码往往有 3 个部分。设置,循环和外部两者。我进行了一些测试,发现当 random 位于循环和设置之外时,对 randomSeed() 的更改不会影响 random 生成的数字。但是,当 random 处于循环中时,它会对来自 randomSeed 的更改做出反应。这是为什么?当 randomSeed 处于设置状态并在全局中调用 random 时,有没有办法让 random 和 randomSeed 一起工作?

标签: c++randomarduinoarduino-unorandom-seed

解决方案


这是我关于如何在 Arduino 或任何微控制器上实现随机数生成器的建议:

  1. 仅在启动时播种随机数生成器(setup()在 Arduino 中)。
     

  2. 使用计时器来消耗随机数,和/或在空闲时消耗随机数。

    这样,即使上电后的状态是可预测的,但状态变化非常迅速,并且变得不可预测(除非以微秒间隔非常密切地监视微控制器状态)。
     

  3. 使用异或 ( ^) 混合来自至少具有一定随机性的源的熵,例如基于雪崩噪声的硬件源

    为获得最佳效果,请仅偶尔进行。我会亲自将熵提供给一个单独的伪随机数生成器,并将其输出混合到主生成器状态。
     

  4. 使用人工输入(按钮按下等,通过高分辨率计时器)作为熵的来源。

    即,以非常高分辨率测量例如连续按钮按下之间的时间,并使用一些最低有效位作为熵来混合到伪随机数生成器状态。

    只需按几下按钮,伪随机数发生器的内部状态基本上就无法预测了。
     

  5. 使用电池供电的 SRAM(通常作为实时时钟模块的一部分提供)存储种子状态以供下次启动。

    从技术上讲,您也可以为此使用 EEPROM 或 Flash,但由于它们的写入周期数有限,因此我不建议使用它们。
     

正如我在其他帖子中提到的那样,我个人不信任内置的伪随机数生成器,而是实现了一种已知的基于线性反馈移位生成器的生成器(Xorshift 变体或 Mersenne Twister)。它们是众所周知的,并且它们的输出随机性被表征。

对于基于 Arduino 的游戏,我可能会使用Xorshift128作为随机数生成器本身,使用 Xorshift64* 从硬件源获取熵并使用其输出不时扰乱主生成器状态(例如,按下按钮时) ,或类似的场合;每秒少于一次)。

我会确保即使在空闲时,生成器的状态也是先进的(通过“消耗”伪随机数;只是将它们扔掉),因此物理世界的时间将成为生成序列的主要因素。

各种微控制器都有不同的定时器和可用的硬件功能,所以我要实现的确切代码肯定取决于所使用的硬件。不幸的是,这意味着代码不能跨硬件移植。即使是很小的、看似无害的变化也可能意味着输出变得非常可预测。我可以为 Arduino Leonardo / Arduino Pro Micro(均基于 ATmega32u4 微控制器)编写一个示例,因为我手头有一个,但如果您使用其他微控制器,代码可能很有趣,但如果您尝试在不知道其细节和行为的情况下将其移植到另一个硬件架构。


要生成一个范围内的伪随机整数,我建议使用排除方法,而不是模 ( %) 方法。排除法确保均匀分布;模法可以对范围较小端附近的某些值产生小的偏差。

排除法的思想很简单。您准确获取所需的位数以覆盖范围,但排除范围之外的值。平均而言,您可能会消耗多达两倍的随机数位,但使用像 Xorshift 这样的快速生成器,这绝对不是问题。

固定 32 位有符号整数范围函数的一般模式是

static inline int32_t  rndrange(void)
{
    uint32_t  u;
    do {
        u = random32() >> SHIFT;
    } while (u > LIMIT);
    return u + MINIMUM;
}

其中random32()返回统一的伪随机 32 位无符号整数,MINIMUM是函数可以返回的最小整数,是函数可以返回MINIMUM + LIMIT的最大整数 ( LIMIT = MAXIMUM - MINIMUM),并且SHIFT

31 if LIMIT == 1      ║  15    LIMIT <= 131071
30    LIMIT <= 3      ║  14    LIMIT <= 262143
29    LIMIT <= 7      ║  13    LIMIT <= 524287
28    LIMIT <= 15     ║  12    LIMIT <= 1048575
27    LIMIT <= 31     ║  11    LIMIT <= 2097151
26    LIMIT <= 63     ║  10    LIMIT <= 4194303
25    LIMIT <= 127    ║   9    LIMIT <= 8388607
24    LIMIT <= 255    ║   8    LIMIT <= 16777215
23    LIMIT <= 511    ║   7    LIMIT <= 33554431
22    LIMIT <= 1023   ║   6    LIMIT <= 67108863
21    LIMIT <= 2047   ║   5    LIMIT <= 134217727
20    LIMIT <= 4095   ║   4    LIMIT <= 268435455
19    LIMIT <= 8191   ║   3    LIMIT <= 536870911
18    LIMIT <= 16383  ║   2    LIMIT <= 1073741823
17    LIMIT <= 32767  ║   1    LIMIT <= 2147483647
16    LIMIT <= 65535  ║   0 if LIMIT <= 4294967295

在 Arduino 或使用 GCC 编译的任何 C 或 C++ 代码中,您可以使用

static inline int  random_intrange(const int  minval,
                                   const int  maxval)
{
    if (maxval > minval) {
        const unsigned int   limit = maxval - minval;
        const unsigned char  shift = __builtin_clz(limit);
        unsigned int         u;
        do {
            u = random_unsigned_int() >> shift;
        } while (u > limit);
        return minval + u;
    } else
        return minval;
}

static inline unsigned int  random_uintrange(const unsigned int  minval,
                                             const unsigned int  maxval)
{
    if (maxval > minval) {
        const unsigned int   limit = maxval - minval;
        const unsigned char  shift = __builtin_clz(limit);
        unsigned int         u;
        do {
            u = random_unsigned_int() >> shift;
        } while (u > limit);
        return minval + u;
    } else
        return minval;
}

只要random_unsigned_int()是一个统一的伪随机数生成器,它返回unsigned ints,从0UINT_MAX,包括在内。


推荐阅读