Я думаю понятие случайных величин вам знакомо: они случайны. Иногда в самодельном устройстве бывает нужна случайная величина, например для какой-нибудь игры или световых эффектов. Монетка, игральный кубик или лото-машина являются отличными источниками случайных чисел, потому что это - физический процесс. Микроконтроллер же не может генерировать настоящие случайные числа, потому что он - точное вычислительное устройство, у которого нет случайностей. Что делать? Использовать такое понятие, как псевдослучайные числа. Псевдослучайное число получается путём различных математических действий с начальным числом, то есть имея начальное число, мы можем сгенерировать на его основе целую кучу других чисел. Но тут есть две проблемы:
- Через какое-то количество итераций (тысяч, миллионов, а может и больше) ряд сгенерированных псевдослучайных чисел начнёт повторяться. Для наших целей это не так страшно, можно об этом не думать
- Генератору псевдослучайных чисел нужно начальное случайное число. И мы можем его получить - об этом ниже
Arduino и случайные числа #
Arduino-фреймворк имеет пару готовых функций для работы с псевдослучайными числами:
random(max)
– возвращает псевдослучайное число в диапазоне от 0 до (max – 1)random(min, max)
– возвращает псевдослучайное число в диапазоне от min до (max – 1)randomSeed(value)
– установить новое начальное число для отсчёта
Запустите следующий код:
void setup() {
Serial.begin(115200);
Serial.println(random(100));
Serial.println(random(100));
Serial.println(random(100));
}
void loop() {
}
В мониторе порта появятся три числа, при перезагрузке МК это будут те же самые три числа. Чтобы запустить новую последовательность, нужно задать стартовое значение для генератора:
- Прочитать сигнал с никуда не подключенного аналогового пина и установить его в качестве начального:
randomSeed(analogRead(0))
. Пин ловит наводки из воздуха и сигнал с него действительно случайный - Если устройство взаимодействует с окружающим миром или с пользователем, то можно при наступлении различных событий (нажатие кнопки, срабатывание датчика, принятие данных, и т.д.) отправлять в
randomSeed()
текущее время с момента запуска программы:randomSeed(micros())
- В esp8266 часто используют вариант c
randomSeed(micros())
сразу после того, как будет установлено подключение к WiFi при запуске программы: время подключение каждый раз сильно отличается
Случайный bool #
Иногда бывает нужен случайный флаг, то есть true
/false
. Делается это очень просто - bool
принимает значение true
при любом отличном от нуля значении. Это можно использовать для получения случайного логического значения с заданной вероятностью! Просто присваиваем логической переменной результат функции random()
, в которую передаём число, обратное вероятности получения false
:
bool rndFlag = random(5); // 1/5 false
Переменная rndFlag
получит значение false
с вероятностью 1/5, то есть 20%. Если нужен true
с вероятностью - используем инверсию:
bool rndFlag = !random(10); // 1/10 true
Теперь переменная rndFlag
получит значение true
с вероятностью 1/10.
Для задания вероятности true
в процентах можно использовать следующую формулу:
random(100) <= percent; // 0-100% true
Аппаратный рандом? #
В уроке про АЦП я показывал, что если пин никуда не подключен - то он ловит "из воздуха" всякие шумы и наводки. Эти шумы имеют природу, близкую к случайной, и было бы глупо этим не воспользоваться!
Первые два бита из результата analogRead()
имеют самый большой шум. Давайте на него посмотрим, выведя несколько результатов в график. Получить первые два бита можно так: analogRead(A0) & 0b11
:
Выглядит весьма случайно, никакой закономерности не прослеживается. Но значения меняются всего от 0 до 3, поэтому можно попробовать их перемножать и складывать, например так:
uint32_t rnd = 0;
for (int i = 0; i < 16; i++) {
rnd *= 4;
rnd += analogRead(A0) & 3;
}
Имеем неплохое рассеяние и огромное количество случайных значений, полученных путём перемножения и сложения "шума". Такой сид можно использовать для стартового значения генератора.