avr: случайнее random()

В простых встраиваемых устройствах есть большая проблема: очень хочется рандом. Нормальный такой рандом. Но если подумать, то контроллеру совершенно не от чего "рандомизировать" генератор случайных чисел. Ибо каждый старт контроллера "жизнь начинается с 0".

Эта проблема всплыла в пресловутом moodlamp (лампа настроения, переливающиеся цвета светодиода). Хочется чтоб "переливы" каждый раз начинались с нового цвета :)

После долгих дум пришёл к следующим идеям

Использовать шумы

Вокруг туча замечательных шумов и наводок! Хоть где то их можно использовать. Например читать с ног ADC и использовать для рандомизации эти значения.

Запоминать значение

По умолчанию библиотека gcc-avr инициализирует random seed value в 1. Можно заменить на что нить другое. Мы можем запоминать в EEPROM и инкрементировать это каждый запуск контоллера. Разумеется инициализировать генератор этим значением через srandom(val)

Использовать температуру

Некоторые контроллеры теперь идут с возможностью измерить температуру кристалла. Или же применение контроллера связанно с измерением каких-либо физических величин. В общем случае это поможет получать хоть сколько-то случайные величины

Использовать ^ (xor)

Вместо установки только что полученного значения с ADC/датчиков можно сделать с этим значением xor. Навроде srandom(newvalue ^ oldvalue).

Почему xor? Эта операция гарантированно возвращает новое значение (сущность побитовой операции такая, можно взглянуть в таблицу истинности)

Читать память

В процессе работы контроллера меняется значение каких-то переменных, мы может хаотично выбирать их из всего пространства доступной памяти. Это несколько может помочь рандомизировать значения. Причём было бы прикольно читать память при каком-нибудь событии, например по таймеру или по внешнему прерыванию

Пример будет ниже в моём решении

Сохранять собранные данные в своём EEPROM

Любые (или часть, или случайная часть) собранных цифр с ADC, датчиков, рандома, памяти сохранять в программируемую память. Это позволит при загрузки использовать больше цифр при начальной инициализации

Ещё...

Можно напридумывать куча способов, рассуждая в этом же направлении :)

Я очень доверяю народу из форумов avrfreaks, поэтому привожу их совет :

int makeRandom(int upper) {
return (int)((double)rand() / ((double)RAND_MAX + 1) * upper);
}

Моё решение

Скомпоновав мысли выше, вывел вот такое:

int makeRandom(int upper) {
return (int)((double)rand() / ((double)RAND_MAX + 1) * upper);
}

void storeRandomToEEPROM() {
uint8_t i;
for(i = 0; i < 20; i++)
eeprom_write_byte((uint8_t *)i, (uint8_t)(makeRandom(1000)%255));
for(i = 0; i < 20; i++)
if (makeRandom(1000) % 2)
eeprom_write_byte((uint8_t *)i, *((uint8_t*)(makeRandom(1000)%255)));
}

void randomizeFromEEPROM() {
uint16_t rndVal = (uint16_t)makeRandom(12345), i;
for(i = 0; i < 10; i++) rndVal ^= eeprom_read_word((uint16_t*)i);
srandom(rndVal);
}

. . .
void init() {
. . .
randomizeFromEEPROM();
storeRandomToEEPROM();
}

ISR (TIM0_OVF_vect) {
static uint16_t softcount=0;

if(++softcount == 0) storeRandomToEEPROM();

. . . .
}

Что получилось

До запуска прошивки состояние EEPROM:

>>> read eeprom 0 30 
0000  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
0010  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff        |..............  |

После первого запуска(и последующих картина памяти будет менятся)

>>> read eeprom 0 30 
0000  00 35 97 bf 07 00 5d bc  53 4a 00 ea 07 13 06 01  |.5....].SJ......|
0010  00 00 dd 5a ff ff ff ff  ff ff ff ff ff ff        |...Z..........  |

При первом запуске генератор инициализируется значениями из EEPROM (при этом xor-ятся друг с другом), и тут же заполняет это место другими значениями, полученными из обычного рандома, после этого поверх случайным образом из случайного места в памяти записывается значение.