【C/C++】raspberry pi picoで乱数のシード値をランダムに取得する方法

raspberry pi pico

乱数を発生させたいとき普通は現在時刻をシード値にするんだけど、
ラズパイピコには現在時刻を取得する方法がない。
だから電源入れるたびに同じパターンの乱数になっちゃう。

だから代案を試してみたよ

何も繋いでないピンのノイズをシード値として使う

概要

空いてるピンのノイズから下位1ビットを切り取って、
1ビットずつ繋げて32ビットのシード値を作る

簡単なソース

#include <stdio.h>
#include <random>
#include "pico/stdlib.h"
#include "hardware/adc.h"

using namespace std;

// ノイズからシード値を取得する関数
uint32_t get_seed_from_noise() {
    adc_init();
    adc_select_input(0); // ADC0(26pin)

    sleep_ms(3000); // ADCの初期化待ち

    uint32_t seed = 0;
    for (int i = 0; i < 32; ++i) {
        seed = (seed << 1) | (adc_read() & 0x01); // ADCから1ビット読み取り
        sleep_us(10); // 少し待機してノイズを取得
    }
    printf("シード値: %u\r\n", seed);
    return seed;
}

int main() {
    stdio_init_all(); // USBシリアル有効化

    // シード値取得
    uint32_t seed = get_seed_from_noise();

    // 乱数生成器の初期化
    mt19937 mt(seed);

    // 乱数生成
    for (int i = 0; i < 10; ++i) {
        printf("乱数 %d: %u\r\n", i + 1, mt());
        sleep_ms(1000); // 1秒待機
    }
}

結果

各回のシード値と乱数の値

1回目

シード値: 4276250083
乱数 1: 2823150713
乱数 2: 2288174597
乱数 3: 2418696248
乱数 4: 2390339823
乱数 5: 3886065031
乱数 6: 3307632585
乱数 7: 3311046633
乱数 8: 528478871
乱数 9: 4121535879
乱数 10: 3663704261

2回目

シード値: 2080047063
乱数 1: 1205165558
乱数 2: 2338461181
乱数 3: 3262844725
乱数 4: 1038273214
乱数 5: 3302093246
乱数 6: 222154999
乱数 7: 1594265656
乱数 8: 3700688378
乱数 9: 1786967674
乱数 10: 3856926273

3回目

シード値: 1876623103
乱数 1: 4058971559
乱数 2: 3379077709
乱数 3: 2107874943
乱数 4: 334960069
乱数 5: 3112600745
乱数 6: 730167502
乱数 7: 3226652477
乱数 8: 1280164834
乱数 9: 3755382957
乱数 10: 3837211427

毎回バラバラの値を発生できてる

ボタン・スイッチをONにしてた長さをシード値として使う

概要

人がボタン・スイッチを一時的にONにして、
そのONにしてた長さをシード値に使う

簡単なソース

#include <stdio.h>
#include <random>
#include "pico/stdlib.h"
#include "pico/platform.h"

using namespace std;

static const uint BUTTON_PIN = 16;  // 物理ピン21 = GP16

// 指定レベルに「安定して」なったことを確認する簡易デバウンス
static void wait_for_level_stable(uint pin, bool level, uint32_t stable_ms = 20) {
    // 目標レベルが連続 stable_ms 続くまで待つ
    while (true) {
        if (gpio_get(pin) == level) {
            uint64_t t0 = time_us_64();
            while (gpio_get(pin) == level) {
                if (time_us_64() - t0 >= (uint64_t)stable_ms * 1000) return;
                tight_loop_contents();
            }
        }
        sleep_ms(1);
    }
}

// ONを入力した長さでシード生成
uint32_t get_seed_from_toggle_duration() {
    // 初期状態がONでもOFFでもHigh(OFF)になるのを待つ
    wait_for_level_stable(BUTTON_PIN, true);

    printf("トグルを ON にしてください(GP16→GNDでLow)。\r\n");
    // High→Lowの立ち下がり待ち(チャタリングを抜ける)
    while (gpio_get(BUTTON_PIN) != 0) tight_loop_contents();
    wait_for_level_stable(BUTTON_PIN, false);
    uint64_t t_low_start = time_us_64();  // Low開始

    printf("次に OFF に戻してください(High)。\r\n");
    // Low→Highの立ち上がり待ち
    while (gpio_get(BUTTON_PIN) != 1) tight_loop_contents();
    wait_for_level_stable(BUTTON_PIN, true);
    uint64_t t_low_end = time_us_64();    // Low終了

    uint64_t duration_us = t_low_end - t_low_start;

    // 64bit長さを32bitに畳み込み(上位と下位をXOR)
    uint32_t seed = (uint32_t)(duration_us ^ (duration_us >> 32));

    printf("ON期間: %llu us\r\n",
           (unsigned long long)duration_us);

    return seed;
}

int main() {
    stdio_init_all();

    // GPIO準備:入力+内部プルアップ(OFFでHigh、ONでLow)
    gpio_init(BUTTON_PIN);
    gpio_set_dir(BUTTON_PIN, GPIO_IN);
    gpio_pull_up(BUTTON_PIN);

    sleep_ms(500); // USBシリアル安定化のため少し待つ

    printf("トグル一往復で乱数シードを決めます。\r\n");

    uint32_t seed = get_seed_from_toggle_duration();

    // 乱数生成器を初期化
    mt19937 mt(seed);
    printf("シード値: %u\r\n", seed);

    // 動作確認として1秒ごとに乱数を出力
    for (int i = 0; i < 10; ++i) {
        printf("乱数 %d: %u\r\n", i + 1, mt());
        sleep_ms(1000);
    }
}

結果

各回のシード値と乱数の値

1回目

ON期間: 2705112 us
シード値: 2705112
乱数 1: 2947653861
乱数 2: 2817416281
乱数 3: 1207261568
乱数 4: 1052065304
乱数 5: 2680958719
乱数 6: 1293200308
乱数 7: 2668840728
乱数 8: 4040694201
乱数 9: 4026817785
乱数 10: 3626918294

2回目

ON期間: 799128 us
シード値: 799128
乱数 1: 3343793046
乱数 2: 2601855074
乱数 3: 3376856194
乱数 4: 1217697657
乱数 5: 800293167
乱数 6: 4056561568
乱数 7: 1793085726
乱数 8: 1672466807
乱数 9: 3389676787
乱数 10: 716017738

3回目

ON期間: 107386 us
シード値: 107386
乱数 1: 3950376988
乱数 2: 1396797211
乱数 3: 1275280842
乱数 4: 2738347141
乱数 5: 3899036843
乱数 6: 3880277418
乱数 7: 620812025
乱数 8: 3015637127
乱数 9: 2802377791
乱数 10: 1295859915

毎回バラバラの値を発生できている

おわりに

空いてるピンのノイズでシード値を作るほうは、
操作が何も必要ないからいいよね

両方を実装して2つの値を加工するような流れにすると
もっとシード値が被りにくいようになるよ

タイトルとURLをコピーしました