乱数を発生させたいとき普通は現在時刻をシード値にするんだけど、
ラズパイピコには現在時刻を取得する方法がない。
だから電源入れるたびに同じパターンの乱数になっちゃう。
だから代案を試してみたよ
何も繋いでないピンのノイズをシード値として使う
概要
空いてるピンのノイズから下位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つの値を加工するような流れにすると
もっとシード値が被りにくいようになるよ