Случайные числа
Я думаю понятие случайных величин вам знакомо: они случайны. Иногда в самодельном устройстве бывает нужна случайная величина, например для какой-нибудь игры или световых эффектов. Монетка, игральный кубик или лото-машина являются отличными источниками случайных чисел, потому что это - физический процесс. Микроконтроллер же не может генерировать настоящие случайные числа, потому что он - точное вычислительное устройство, у которого нет случайностей. Что делать? Использовать такое понятие, как псевдослучайные числа. Псевдослучайное число получается путём различных математических действий с начальным числом, то есть имея начальное число, мы можем сгенерировать на его основе целую кучу других чисел. Но тут есть две проблемы:
- Через какое-то количество итераций (тысяч, миллионов, а может и больше) ряд сгенерированных псевдослучайных чисел начнёт повторяться. Для наших целей это не так страшно, можно об этом не думать.
- Генератору псевдослучайных чисел нужно начальное случайное число. И мы можем его получить - об этом ниже.
Arduino и случайные числа
Ардуино имеет пару готовых функций для работы с псевдослучайными числами, давайте на них посмотрим:
random(max)
– возвращает псевдослучайное число в диапазоне от 0 до (max – 1
). Принимаетunsigned long
*random(min, max)
– возвращает псевдослучайное число в диапазоне отmin
до (max – 1
). Принимаетunsigned long
*randomSeed(value)
– установить новое начальное число для отсчёта,value
– любое число типаunsigned long
*
(*) - от 0
до 4 294 967 295
Как генерировать случайные числа, чтобы последовательность каждый раз была новая? Есть варианты:
- Прочитать сигнал с никуда не подключенного аналогового пина и установить его в качестве начального:
randomSeed(analogRead(0))
. Пин ловит наводки из воздуха и сигнал с него действительно случайный. Подробнее об этом ниже. - Если устройство взаимодействует с окружающим миром или с пользователем, то можно при наступлении различных событий (нажатие кнопки, срабатывание датчика, принятие данных, и т.д.) скармливать в
randomSeed()
текущее время с момента запуска программы:randomSeed(micros())
. - В esp8266 часто используют вариант c
randomSeed(micros())
сразу после того, как будет установлено подключение к WiFi при запуске программы: время подключение каждый раз сильно отличается.
Аппаратный рандом?
Помните, в уроке про цифровые пины я говорил, что если пин никуда не подключен - то он ловит "из воздуха" всякие шумы и наводки? Эти шумы имеют природу, близкую к случайной, и было бы глупо этим не воспользоваться! Давайте посмотрим, какие значения можно получить с никуда не подключенного аналогового пина, опрашивая его обычным analogRead();
void loop() { Serial.println(analogRead(A0)); delay(100); }
Синусоида? Вполне логично, в стенах куча проводов под напряжением, вот они и наводят помехи на пин. Вполне можно сделать на этой основе настоящий аппаратный рандом.
Первые два бита из результата analogRead()
имеют самый большой шум. Давайте на него посмотрим, выведя несколько результатов в график. Получить первые два бита можно так: analogRead(A0) & 3
for (int i = 0; i < 300; i++) { Serial.println(analogRead(A0) & 3); }
Выглядит весьма случайно, никакой закономерности не прослеживается. Но значения меняются всего от 0 до 3, поэтому можно попробовать их перемножать и складывать, примерно так:
uint32_t seed = 0; for (int i = 0; i < 16; i++) { seed *= 4; seed += analogRead(A0) & 3; randomSeed(seed); }
Результат можно использовать для получения случайного значения для генератора случайных чисел при запуске программы.
Напоследок давайте посмотрим, какой результат даёт такой аппаратный рандом. График я построил в excel, чтобы более чётко видеть рассеивание полученного случайного значения:
Имеем практически равномерное рассеяние и огромное количество случайных значений, полученных путём перемножения и сложения "шума".
Случайный bool
Иногда бывает нужен случайный флаг, то есть true
/false
. Делается это очень просто: в уроке про условия я рассказывал, что bool
принимает true
при любом отличном от нуля значении. Это можно использовать для получения случайного логического значения с заданной вероятностью! Просто присваиваем логической переменной результат функции random()
, в которую передаём число, обратное вероятности получения false
:
bool rndFlag = random(5);
Переменная rndFlag
получит значение false
с вероятностью 1/5, то есть 20%. Если нужен true
с низкой вероятностью - используем инверсию:
bool rndFlag = !random(10);
Теперь переменная rndFlag
получит значение true
с вероятностью 10%.
Библиотеки
Стандартный рандом выполняется относительно долго (~90 мкс), иногда это может мешать. Можно использовать быстрый рандом, реализацию я взял из библиотеки FastLED и оформил в виде отдельной библиотеки, документация и примеры есть по ссылке.
Видео
Полезные страницы
- Набор GyverKIT – большой стартовый набор Arduino моей разработки, продаётся в России
- Каталог ссылок на дешёвые Ардуины, датчики, модули и прочие железки с AliExpress у проверенных продавцов
- Подборка библиотек для Arduino, самых интересных и полезных, официальных и не очень
- Полная документация по языку Ардуино, все встроенные функции и макросы, все доступные типы данных
- Сборник полезных алгоритмов для написания скетчей: структура кода, таймеры, фильтры, парсинг данных
- Видео уроки по программированию Arduino с канала “Заметки Ардуинщика” – одни из самых подробных в рунете
- Поддержать автора за работу над уроками
- Обратная связь – сообщить об ошибке в уроке или предложить дополнение по тексту ([email protected])