【Arduino 真性乱数】How to get a true random number without using analogRead
Arduino で真性乱数を得る
真性乱数とは真に無作為な数にして、真ならずして無作為な数はこれを擬似乱数と言う。random()
を標準に備えると雖も、これは擬似乱数に外ならず、同じ条件であれば同じ結果しか得られない。randomSeed()
に
analogRead()
調べると、アナログ入力が不安定であることに基づき、analogRead()
の値を活用する方法が見られる。この方法は簡単だが、ピンの状態を正しく理解した上で管理する必要がある。言わずもがな
Arduino系機器のピン配置
Arduino UNO Rev3
引用:https://content.arduino.cc/assets/A000066-pinout.png
Portenta C33
引用:https://docs.arduino.cc/static/6d64f491fd82779f1d4adf5069014c24/4ef49/ABX00074-pinout-MKR.png
Arduino Nano RP2040 Connect
引用:https://docs.arduino.cc/static/a4d65079ebe3058e37153eb0f93890a8/4ef49/pinout.png
Arduino Nano ESP32
引用:https://d2air1d4eqhwg2.cloudfront.net/markdownx/ca52a740-8594-4741-89ad-1715bee94cb5.JPG
M5Stack CoreS3
引用:https://m5stack.oss-cn-shenzhen.aliyuncs.com/resource/docs/products/core/CoreS3/S3.jpg
Raspberry Pi Pico 2W
引用:https://docs.sunfounder.com/projects/pico-2w-kit/en/latest/_images/pico-2-w-pinout.png,
https://datasheets.raspberrypi.com/picow/pico-2-w-pinout.pdf
Seeed Studio XIAO SAMD21
引用:https://files.seeedstudio.com/wiki/Seeeduino-XIAO/img/Seeeduino-XIAO-pinout-1.jpg
Wio Terminal
引用:https://files.seeedstudio.com/wiki/Wio-Terminal/img/WioT-Pinout.jpg
ピンの数や役割は機器によって異なる上、情報の質も異なる。これらを誰もが間違うことなく運用できる確証はない。斯く言う私こそが、M5Stack
のピンを理解できていない一人である。
ピンの扱いを誤ることは、予期せぬ動作や故障を引き起こしかねない危険な行為である。
millis()
・micros()
第二の手段として、millis()
やmicros()
を用いることができる。これらは起動時点からの経過時間を得るものにして、
/*
ミリ秒とマイクロ秒の値を掛け合わせて値を大きくする
且つ絶対値(abs)を取ることで負数になることを回避する
*/
uint32_t randomNumber = abs(millis()*micros());
Serial.printf("random: %d\n", randomNumber);
random: 1322828900
random: 1369241640
random: 1232397480
random: 608497500
random: 1210286000
random: 1254704640
random: 722631750
random: 1081848560
全て異なる値を得たことが分かる。
真相
実を言うと、millis()
やmicros()
を用いて真性乱数を得ることはできない。単なる起動時間であれば、寸分違わず同じ値を取る。ではなぜ上の実行結果が真性乱数になっているのかと言うと、真性乱数を齎している要因が別にあるためである。
void setup() {
Serial.begin(115200);
while(!Serial);
}
Serial
とは、Serial1
などが使われる)。従ってwhile(!Serial)
とは、「シリアルモニターが開かれるまで処理を防ぐ」ことを意味する。シリアルモニターを開く前に処理が進み、初めに出力された内容が見えなくなってしまう⋯といったことを防ぐことができる。
結果として、縦令常にシリアルモニターを開いていようとも、処理が再開されるまでに誤差を生じていたらしい。その上、使用する環境次第では、while(!Serial)
は必ずしも機能しない。故に、起動時間で真性乱数を得る方法は「極めて限られた場面」でしか使うことができず、汎用性はないと言える。
本題:記憶領域
ここまで用いてきたアナログ入力やシリアル通信には本来の使い方というものがあり、そちらを優先する場合には使えなくなる。では、記憶領域ならば、本来の使い方をしたまま真性乱数を得られないだろうか。
SRAM
AVR
int freeRam() {
extern int __heap_start,*__brkval;
int v;
return (int)&v - (__brkval == 0
? (int)&__heap_start : (int) __brkval);
}
ARM
一方で、__heap_start
を使うことができず、全く別の手法を取る。
extern "C" char* sbrk(int incr);
int freeRam() {
char top;
return &top - reinterpret_cast<char*>(sbrk(0));
}
このように記憶領域という物理的な環境に依存するため、販売ページやデータシートで、使用する
ここでは、
コアプロセッサ ARM Cortex-M4F
これまでの経験から、シリアル通信に依存しない方法で確認できるとよいことが分かっている。幸い、
#include "TFT_eSPI.h"
/* 画面制御 */
TFT_eSPI tft;
extern "C" char* sbrk(int incr);
/* なんとなく大域変数にしてみた */
static uint32_t u32Data = 0;
void getRandom() {
char top;
for (char* pcHeap = reinterpret_cast<char*>(sbrk(0)); pcHeap < ⊤ pcHeap++) {
u32Data = u32Data ^ (uint32_t)*pcHeap; // XOR
}
}
void setup() {
getRandom();
char acText[64];
snprintf(acText, 64, "data: %d", u32Data);
tft.begin();
tft.setRotation(3); // 画面の向き
tft.setTextSize(3); // 文字の大きさ
tft.setTextColor(TFT_BLACK); // 文字の色を黒にする
tft.fillScreen(TFT_WHITE); // 画面全体を白にする
tft.drawString(acText, 60, 100); // acTextを画面に表示する
}
void loop() {}
実行結果として画面に表示された値を記録した。
回 | 値 |
---|---|
1 | 182 |
2 | 184 |
3 | 189 |
4 | 131 |
5 | 141 |
6 | 99 |
7 | 59 |
8 | 182 |
9 | 208 |
10 | 213 |
他も試した記録
「
通常のEEPROM.h
という標準ライブラリーが使えるとある。しかし、
#include <EEPROM.h>
^~~~~~~~~~
compilation terminated.
exit status 1
Compilation error: EEPROM.h: No such file or directory
ATSAMD51P19
と称することが分かる。このデータシートを見ると、SmartEEPROM
というものが確かに存在する。
つまり、EEPROM.h
はないらしい。そこで、次のものが使える。
「ATSAMD51P19
も該当すると思われる。FlashStorage_SAMD
は
サンプルプログラムのdelay()
の値だけ変えて、
/******************************************************************************************************************************************
EEPROM_read.ino
For SAMD21/SAMD51 using Flash emulated-EEPROM
The FlashStorage_SAMD library aims to provide a convenient way to store and retrieve user's data using the non-volatile flash memory
of SAMD21/SAMD51. It now supports writing and reading the whole object, not just byte-and-byte.
Based on and modified from Cristian Maglie's FlashStorage (https://github.com/cmaglie/FlashStorage)
Built by Khoi Hoang https://github.com/khoih-prog/FlashStorage_SAMD
Licensed under LGPLv3 license
Orginally written by A. Christian
Copyright (c) 2015-2016 Arduino LLC. All right reserved.
Copyright (c) 2020 Khoi Hoang.
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
as published bythe Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along with this library.
If not, see (https://www.gnu.org/licenses/)
******************************************************************************************************************************************/
/*
EEPROM Read
Reads the value of each byte of the EEPROM and prints it to the computer.
This example code is in the public domain.
*/
//#define EEPROM_EMULATION_SIZE (4 * 1024)
// Use 0-2. Larger for more debugging messages
#define FLASH_DEBUG 0
// To be included only in main(), .ino with setup() to avoid `Multiple Definitions` Linker Error
#include <FlashStorage_SAMD.h>
// start reading from the first byte (address 0) of the EEPROM
int address = 0;
byte value;
void setup()
{
Serial.begin(115200);
while (!Serial);
delay(200);
Serial.print(F("\nStart EEPROM_read on ")); Serial.println(BOARD_NAME);
Serial.println(FLASH_STORAGE_SAMD_VERSION);
Serial.print("EEPROM length: ");
Serial.println(EEPROM.length());
}
void loop()
{
// read a byte from the current address of the EEPROM
value = EEPROM.read(address);
Serial.print(address);
Serial.print("\t");
Serial.print(value, DEC);
Serial.println();
if (++address == EEPROM.length())
{
address = 0;
}
/***
As the EEPROM sizes are powers of two, wrapping (preventing overflow) of an
EEPROM address is also doable by a bitwise and of the length - 1.
++address &= EEPROM.length() - 1;
***/
delay(10); // 500では遅すぎたため10に変更した
}
その結果は、案の定といった様子であった。
Start EEPROM_read on Unknown SAMD51 board
FlashStorage_SAMD v1.3.2
EEPROM length: 1024
0 255
1 255
2 255
3 255
4 255
5 255
6 255
7 255
8 255
9 255
10 255
11 255
12 255
13 255
14 255
15 255
16 255
17 255
18 255
19 255
20 255
21 255
22 255
23 255
24 255
25 255
26 255
27 255
28 255
︙
(中略)
︙
995 255
996 255
997 255
998 255
999 255
1000 255
1001 255
1002 255
1003 255
1004 255
1005 255
1006 255
1007 255
1008 255
1009 255
1010 255
1011 255
1012 255
1013 255
1014 255
1015 255
1016 255
1017 255
1018 255
1019 255
1020 255
1021 255
1022 255
1023 255
0 255
1 255
2 255
︙
(以下略)
跋:注意点
アナログ入力に特別の非があるわけではないが、高々乱数のため態々しくピンを使うことに対し、猜疑や違和の感を拭うことを得ず、終に別の方法を見つけるに至った。
しかしながら、不安定な値の入力にせよ、記憶領域の直接的な読み取りにせよ、予期せぬ動作を引き起こす危険な行為であることには違いない。特に、記憶領域を読み取る際に使った手法は代表的な危険行為である。
for (char* pcHeap = reinterpret_cast<char*>(sbrk(0)); pcHeap < ⊤ pcHeap++)
「pcHeap
という
こうしたものを特に「
反対に、意味のある位置を示すことが保証されるものは「&top
のような「参照」がこれに該当する。
危険 | 安全 |
---|---|
|
|
本記事で扱った真性乱数自体が「決まった値が保証されない」ものであるから、安全が保障されない行為に手を染めざるを得ないと留意する必要がある。
Discussion