Autor: Dondu
Niniejszy artykuł jest częścią cyklu Efektywne Planowanie Projektu i przedstawia jedno z możliwych rozwiązań generatora liczb losowych, bez wykorzystania standardowej i pamięciożernej funkcji rand() z biblioteki stdlib. Więcej rozwiązań znajdziesz tutaj: EPP: Generator liczb losowych
Przygotowałem dla Was rozwiązanie wykorzystujące szumy powstające przy pomiarze przetwornikiem analogowo-cyfrowym (ADC).
Szum ADC - co to jest?
Przetwornik ADC pozwala na zmierzenie poziomu sygnału analogowego np. sinusoidy. Pomiar ten trwa pewien czas na przykład 20μs. W tym czasie:
Rys. Sukcesywna aproksymacja |
- napięcie zasilające ADC oraz napięcie referencyjne (odniesienia) mogą nie być stabilne (minimalne zmiany)
- mogą powstawać zakłócenia na wejściu pomiarowym ADC,
- technika pomiaru (sukcesywna aproksymacja) także nie jest idealna.
Te zjawiska zwykle są niepożądane i eliminowane za pomocą odpowiednich technik projektowania i budowy urządzeń elektronicznych. Niedokładności pomiarów nazywa się szumem ADC który ujawnia się w szczególności na najmłodszych bitach wyniku pomiaru, poprzez przyjmowanie przez nie przypadkowych stanów logicznych.
Rys. Przykład szumu ADC |
Jak wykorzystać szum?
Skoro szum jest przypadkowy, to można by go wykorzystać do budowy generatora liczb losowych.
Niestety nie wystarczy odczytać pomiar ADC, ponieważ losowy stan mają najczęściej tylko bity najmłodsze (patrz animacja powyżej).
Dlatego też bezpiecznie przyjmiemy, że naszą liczbę losową tworzyć będziemy używając tylko najmłodszego bitu ADC (gdyż jest to najbardziej losowy bit). Liczbę tę będziemy składać za każdym nowym pomiarem wykonanym przez ADC w następujący sposób :
Rys. Algorytm tworzenia liczby losowej za pomocą ADC |
Schemat
Wykorzystamy jeden kanał ADC. Aby układ był najbardziej niestabilny pin wejściowy ADC pozostawimy niepodłączony, by zbierał zakłócenia.
Schemat zawiera niezbędne elementy do prawidłowej pracy mikrokontrolera, które opisałem tutaj:Minimalne podłączanie pinów
Program
Program może być rozwiązany na wiele różnych sposobów. Ja przyjąłem wersję najprostszą:
- ADC używany jest tylko do generowania liczby losowej,
- liczba ma być generowana w tle (w przerwaniu) i zapisywana do globalnej zmiennej liczba_RND,
- ADC pracować będzie w trybie FREE RUNNING (pomiar za pomiarem),
- przerwanie z ADC może być używane także do innych celów.
Niezbędne definicje początkowe:
#include <avr/io.h> #include <util/delay.h> #include <avr/interrupt.h> volatile char liczba_RND; //liczba losowa dostępna zawsze i wszędzie
1. Inicjalizacja ADC
1.1 Wybieramy źródło napięcia odniesienia, z pinu AREF:
1.2 Wybieramy kanał na pinie ADC0:
1.3 Wybieramy wyrównanie wyniku pomiaru ADC do prawej (domyślny):
1.4 Ustawiamy preskaler ADC
Ponieważ w naszym przypadku mikrokontroler taktowany jest zegarem 8MHz, a ADC mikrokontrolera ATmega8 nie może być taktowany szybciej niż 1MHz, stąd musimy zmniejszyć częstotliwość, którą taktowany będzie ADC. Do tego celu wykorzystamy preskaler ADC ustawiając go na 8:
Rys. Preskaler ADC dla Atmega8 |
Ponieważ jeden pomiar ADC trwa 13 cykli zegara ADC, to policzmy ile czasu będzie trwał jeden pomiar:
Czyli co 13µs będziemy mieli nową pseudolosową liczbę. Jednakże ze względu, że:
- za każdym pomiarem liczba_RND jest przesuwana o 1 bit w lewo, a najmłodszy jej bit jest ustawiony losowo,
- oraz liczba_RND zawiera 8 bitów,
UWAGA!!!
Dla 8 bitowej liczba_RND:
- odczytując ją rzadziej niż co 104µs masz pewność, że jest liczbą losową.
- odczytując ją częściej niż 140µs, ale rzadziej niż 13µs otrzymujesz liczbę pseudolosową.
- odczytując ją częściej niż co 13µs nie masz pewności, że liczba będzie się różnić od ostatnio odczytanej.
To są istotne ograniczenia tak zbudowanego generatora liczb losowych. Oczywiście gdy potrzebujesz na przykład tylko 4 bitową liczbę losową, czasy te będą odpowiednio krótsze.
Jeżeli te czasy są dla Ciebie zbyt długie, możesz zmienić poniższy program tak, by wykorzystać możliwość przekroczenia dopuszczalnych parametrów ADC, podobnie jak to zrobił Drzasiek testując przetwornik ADC w ATmega8: ADC - Ile da się wycisnąć? - czyli eksperymenty z ADC.
Tworzymy funkcję inicjującą ADC:
void ADC_inicjuj(void) { //funkcja inicjuje ADC w tryb Free Running, //wywołując jednocześnie pierwszy pomiar //ustawiamy rejestr ADMUX //wybierz kanał pomiarowy nr 0 czyli pin PC0, MUX3:0=0 //wyrównaj wynik pomiaru do prawej, ADLAR=0 //napięcie odniesienia AVcc z kondensatorem na AREF //jeżeli nie wykorzystujesz ADC do innych celów, to możesz //nie dodawać kondensatora ADMUX = (1<<REFS0); //ustawiamy rejestr ADCSRA ADCSRA |= (1<<ADPS1)|(1<<ADPS0); //ustaw preskaler ADC na 8 ADCSRA |= (1<<ADEN); //włącz ADC ADCSRA |= (1<<ADFR); //włącz tryb FREE RUNNING ADCSRA |= (1<<ADIE); //włącz przerwania z ADC ADCSRA |= (1<<ADSC); //rozpocznij pierwszy pomiar ADC }
Po dokonaniu pomiaru przez ADC, wywoływane jest przerwanie w celu odczytu wyniku i wygenerowania nowej liczba_RND:
ISR(ADC_vect) { //--- Obsługa przerwania po dokonaniu pomiaru przez ADC liczba_RND = (liczba_RND<<1); //przesuń liczba_RND o 1 bit w lewo //odczytaj ADC, pozostaw tylko najmłodszy bit //i skopiuj ten bit na najmłodszy bit zmiennej liczba_RND liczba_RND |= ADC & 0x01; //tutaj możesz dodać kod wykorzystujący przerwanie z ADC //także do innych celów }
I wreszcie główna część programu:
//--- Program główny ------------- int main(void) { sei(); //włącz globalne przerwania ADC_inicjuj(); //inicjuj ADC while(1) { //tutaj nie ma nic, ponieważ generator jest w funkcji przerwania z ADC //czyli działa w tle, a liczba losowa jest dostępna //z dowolnego miejsca programu i w dowolnym momencie //w zmiennej globalnej: liczba_RND //oczywiście możesz tutaj zamieścić swój program :-) } }Pobierz kompletny program: main11.c
Taki kod zajmuje:
- Program: 508 bytes
- Data: 1 bytes
Oooo! Jaka kolosalna różnica w porównaniu do 930 bajtów, które funduje nam funkcja rand()
Poniżej projekt pokazujący na wyświetlaczu losowane liczby uzyskane za pomocą przedstawionego wyżej rozwiązania:
Aby jeszcze zwiększyć niestabilność generatora liczb losowych, możesz zrobić prosty zewnętrzny generator szumu i podłączyć pod wejście ADC mikrokontrolera:
Schemat 2 - Generator szumu. |
źródło: Hardware Random Number Generator
Powyższy generator zaprojektowany jest do napięcia 12V, będziesz musiał więc pokombinować, by dostosować go do swoich wymagań. Ale jest on na tyle prosty, że bez problemu nawet drogą eksperymentalną to osiągniesz.
Możesz zauważyć, że kolektor trazystora Q1 także jest niepodłączony (NC - not connected) i podobnie jak ADC0 ze schematu 1, zbiera zakłócenia, które są podstawą generowania losowego szumu.
Taki generator (dostosowany do napięcia zasilania Twojego mikrokontrolera) możesz podłączyć bezpośrednio pod dowolny pin mikrokontrolera (ponieważ mają one na wejściach przerzutniki Schmitta) i sprawdzać jego stan (0 lub 1) składając liczbę z bitów, w sposób opisany wyżej.
Inne przykłady generatorów szumu
Przykłady prostych układów generatorów szumu możesz znaleźć w tym opracowaniu: Random Number Generation with a Simple Transistor Junction Noise Source (kopia)
Podsumowanie
Jeżeli masz wolny przetwornik ADC, to problem masz z głowy.
Jeżeli wykorzystujesz ADC do innych celów, to możesz kombinować ze zmianami kanałów.
Jeżeli losowe liczby są Ci potrzebne bardzo rzadko, możesz maksymalnie zwolnić ADC preskalerem lub wywoływać nowe pomiary programowo, zamiast w trybie Free Running.
Generalnie ... rusz głową i dostosuj do swoich potrzeb! :-)
Zalety:
- oszczędność: 930 - 508 = 422 bajtów
- pełna losowość zależna jedynie od łapanych przez układ zakłóceń,
- bardzo krótki kod wynikowy (małe zużycie pamięci programu),
- możliwość wykorzystania przerwania z ADC do innych celów (eliminacja drgań styków przycisków, multipleksowanie wyświetlaczy, zegar czasu rzeczywistego RTC, itp.).
- liczby losowe są uzyskiwane w odstępach czasowych
- zajęty ADC (co najmniej jeden kanał).
Powyższe podejście jest tylko przykładem i może być dowolnie zmieniane tak, aby pozbyć się niektórych z wyżej wymienionych wad, w zależności od Twoich potrzeb.
Możesz także zwiększyć ilość bitów w liczbie losowej.
Bardzo dobra technika.
OdpowiedzUsuńSwego czasu gdy walczyłem z ADC zauważyłem tą właśnie "szumową" naturę gdy nie jest podłączony pin.
Brawo! :)
Pozostawienie wejścia ADC niepodłączonego jest tylko z pozoru dobrym pomysłem. Jakakolwiek upływność DC spowodowana:
OdpowiedzUsuń- wzrostem prądów wstecznych diod zabezpieczających lub tranzystorów wyjściowych pod wpływem temperatury
- konstrukcją PCB (ścieżki obok, wilgoć itp)
spowoduje nasycenie wejścia i odczyt liczb złożonych z samych jedynek (raczej) lub z samych zer a to nie wygląda losowo. Jednak coś z tym wejściem trzeba zrobić. Proponuję dzielnik z rezystorów 2Meg lub większych do masy i np. do VREF.
Poza tym jesteście trochę niesprawiedliwi dla funkcji rand ;) W każdym nietrywialnym programie przyrost kodu będzie mniejszy, bo funkcje wołane przez rand i tak są już gdzieś używane. U moim teście (program miernika wielozakresowego, ATMega8) kod wydłużył się o 308 bajtów - niemało, ale do przełknięcia.
No i samo zadanie jest bardzo słabo określone.
Problem braku generowania szumu przez ADC, w przypadku opisanym przez Ciebie, jest hipotetycznie możliwy.
OdpowiedzUsuńPiszę hipotetycznie, ponieważ moim zdaniem, i z mojej praktyki zjawisko, które opisałeś nie występuje niezależnie czy używam PICów, AVRów czy w przeszłości zewnętrznych ADC.
Co do niesprawiedliwego osądu rand() vs ilość kodu vs bardziej skomplikowane programy, oczywiście może być tak, że oszczędność pamięci będzie nieco mniejsza. Jednakże założenia tego odcinka EPP dotyczą projektów, w których biblioteka stdlib może nie być w ogóle wykorzystywana do innych celów, w związku z czym oszczędności będą znaczne, jak pokazane w artykule.
Wydaje mi się, że nie do końca zrozumiałeś ideę i założenia tego odcinka EPP, a konkretnie tego zdania cyt:
500 bajtów - to duża strata czy mała?
To zależy jaki mikrokontroler używasz i ile masz wolnej pamięci - czasami walczy się o kilkadziesiąt bajtów, a w skrajnych przypadkach o każdy bajt.
A pamiętać należy, że AVR to także ATtiny13 ze swoim 1k pamięci programu, gdzie każdy bajt jest na wagę złota :-)
Mam propozycję.
Ponieważ nikt nie nadesłał, żadnego innego rozwiązania, niż to zaprezentowane przeze mnie, proponuję, abyś opracował swoją wersję wraz z proponowanym dzielnikiem rezystorowym i zamieścimy ją razem ze wszystkimi twoimi uwagami.
Zadanie jest tak określone, aby pozostawić autorom szerokie pole do popisu - więc zapraszam do jego rozwiązania: EPP: Generator liczb losowych
Jak w ATmega32A włączyć tryb FREE RUNNING? A raczej jak zastąpić tą linijkę kompatybilną dla tego procesora?
OdpowiedzUsuńW mikrokontrolerze ATmega32 tryb Free Running mode, włącza się w rejestrze SFIOR bitami ADTS2:0 ustawionymi na 0. Ponieważ jest to domyślny stan tych bitów, to po resecie masz wybrany właśnie ten tryb pracy ADC.
OdpowiedzUsuńNatomiast w rejestrze ADCSRA musisz ustawić automatyczne wyzwalanie na bicie ADATE:
Bit 5 – ADATE: ADC Auto Trigger Enable
When this bit is written to one, Auto Triggering of the ADC is enabled. The ADC will start a conversion on a positive edge of the selected trigger signal. The trigger source is selected by setting the ADC Trigger Select bits, ADTS in SFIOR.
czyli zamiast:
ADCSRA |= (1<<ADFR); //włącz tryb FREE RUNNING
powinieneś wstawić:
ADCSRA |= (1<<ADATE); //włącz automatyczne wyzwalanie
I to chyba wszystko.
A nie lepiej XORować i przesuwać, zamiast brać LSB? Przecież szum występuje także na innych bitach. Proponuję:
OdpowiedzUsuńRND = (RND ^ wynikADC) << 1;
Ale na ostatnim bicie (LSB), jest najbardziej losowy.
OdpowiedzUsuńNo i? Na innych jest może i mniej losowo, ale nawet jak się trafi: 0000000x to co? Po prostu sxoruje z zerami. Jeśli tam też będzie losowe, np.: 00000xyz, to sytuacja się wg mnie poprawia.
OdpowiedzUsuńZresztą, przy założeniu, że bity to zmienne niezależne mamy coś takiego:
### POCZĄTEK FORMALNEGO DOWODU ###
Zamiast zapisywać prawdopodobieństwo standardowo, zapiszemy je jako 1/2 + x.
Prawdopodobieństwo tego, że wypadnie jedynka na LSB to 1/2 + q, gdzie -1/2 =< q =< 1/2. np. dla 1/3 mamy: 1/3 + q = 1/2, czyli q = 1/3 - 1/2 = 2/6 - 3/6 = -1/6. Ponieważ prawdopodobieństwa są z przedziału [0;1] to mamy:
1/2 + q e [0;1]
q e [-1/2;1/2] //e to należy
Prawdopodobieństwo jedynki będącej wynikiem operacji XOR to, P(01) + P(10) = (1/2 - p)(1/2 + q) + (1/2 + p)(1/2 - q) = ... = 1/2 - 2pq
Teraz pytanie, która sytuacja jest bardziej losowa? Co to znaczy losowa? Losowość (a wł. jej brak) możemy mierzyć odległością prawdopodobieństwa od 1/2:
* |1/2 - 1/2| = |0| = 0 - losowość pełna
* |0 - 1/2| = |-1/2| = 1/2 - brak losowości
więc:
p e [-1/2;1/2]
2p e [-1;1]
czyli mnożąc przez 2p uzyskam tyle samo, bądź mniej.
więc:
|2pq| =< |q|, czyli:
### WNIOSEK ###
Przy założeniu zmiennych niezależnych, wynik XORa bitu 1 i 0 jest bardziej losowy od wartości LSB.
Żadne rozwiązanie programowe nie jest w pełni losowe.
OdpowiedzUsuńWprowadzenie szumu otoczenia, na które nie mamy wpływu, należy traktować jak element całkowicie losowy.
Jeżeli więc każdy bit liczby, jest pobierany z losowego sprzętowego generatora szumu, to wynik jest w 100% losowy. Oczywiście można dyskutować nad tym, na ile otoczenie będzie wpływać na wyniki losowości generatora szumu :-)
Nie analizowałem Twojej propozycji, która być może daje całkiem dobry poziom losowości, podobnie jak drugie rozwiązanie kolegi miszcz310, które można znaleźć tutaj: EPP: Generator liczb losowych.
Ja po prostu uzasadniłem, że pomimo, iż LSB jest najbardziej losowy, to o ile wartości bitów są niezależne, to warto XORować, a nie brać LSB.
OdpowiedzUsuńTo jest odnośnie:
matma6 pisze...
A nie lepiej XORować i przesuwać, zamiast brać LSB? Przecież szum występuje także na innych bitach. Proponuję:
RND = (RND ^ wynikADC) << 1;
1 stycznia 2013 21:50
Blogger Dondu pisze...
Ale na ostatnim bicie (LSB), jest najbardziej losowy.
Tak jak pisałem, nie analizowałem Twojej propozycji. Muszę to odłożyć w czasie, bo mam kupę własnej roboty, a obiecałem niedawno, że szybko opracuje następny artykuł o BLDC oraz opublikuję czekający o MOSFET (janbernat). Postaram się przeanalizować Twoją propozycję za jakiś czas.
OdpowiedzUsuń"Rys. Algorytm tworzenia liczby pseudolosowej za pomocą ADC" zły podpis. takim sposobem to nie jest liczba pseudolosowa tylko losowa :P
OdpowiedzUsuńDziękuję za zwrócenie uwagi. Oczywiście losowa, zgodnie z tytułem artykułu :-)
OdpowiedzUsuńWitam, chciałem skorzystać z kodu do generowania liczb losowych i powiem że działa świetnie, ale ma mały problem. Chciałem wygenerować sobie cztery liczby, zapisać je do eepromu, potem odczytać i wyświetlić, jakież było moje zdziwienie gdy po odczytaniu eepromu dostawałem albo 0 abo 255, natomiast jeśli najpierw wygeneruje liczby i wyświetlę na lcd, potem zapiszę do eepromu i odczytam to wszystko jest w porządku. Po resecie zapisane losowe liczby w eepromie są poprawne. Wygląda na to że liczby nie są generowane jeśli ich najpierw nie wyświetlę, dlaczego ? walczę już z tym dwa dni, w docelowym układzie nie będzie wyświetlacza.
OdpowiedzUsuńdodam może kawałek kodu, wystarczy okdomentować pierwszą linię i wszystko działa jak powinno
Usuńfor (i=0;i<4;i++){
// lcd_int(liczba_RND);
syncword[i]=liczba_RND;
eeprom_write_byte(wsk[i], syncword[i]);
_delay_ms(5);
}
Witaj.
UsuńCzy liczba_RND jest definiowana z użyciem volatile? Jeżeli nie w tym leży problem, to potrzebna będzie analiza całego Twojego programu. W takim przypadku dobrze byłoby, abyś założył temat na forum.
Czy przypadkiem zbieranie szumów na jednym z wyprowadzeń ADC nie będzie wpływać na dokładność pomiaru reszty multipleksowanych kanałów ADC?
OdpowiedzUsuńPytam bo w artykule o ADC jest powiedziane, że nawet nieużywane kanały ADC powiiny zostać jakoś spolaryzowane.
Bardzo dobre pytanie :-)
UsuńTak, jest możliwy wpływ takiego pinu na sąsiednie piny. Jak duży? To zależy od projektu i środowiska pracy. Nie da się tutaj generalizować skutków, tym bardziej, że nie zawsze pomiary ADC wykonywane są z największą możliwą rozdzielczością.
Aby temu problemowi zaradzić, wystarczy na czas pomiaru innych pinów ADC, pin służący do generatora losowego ustawić jako wejście z podciągniętym rezystorem pull-up. W ten sposób w czasie trwania pomiarów za pomocą innych pinów ADC, pin służący do generatora losowego nie będzie miał żadnego wpływu na inne kanały ADC.
Dodam jeszcze:
UsuńGENERALNA ZASADA
Dany pin możemy wykorzystywać tylko przez ułamek sekundy, a później wyłączać lub zmieniać jego funkcjonalność.
Projektując należy brać tę zasadę pod uwagę. Często początkujący projektanci z braku doświadczenia (co jest naturalne) przyjmują, że w ich projekcie jeden pin, to jedna funkcjonalność ustawiona raz na samym początku programu i nie zmieniana w trakcie jego wykonywania.
I jeszcze jedna uwaga.
UsuńCzęść mikrokontrolerów ma kanały ADC podzielone na dwie części. Na przykład w ATmega8 wewnętrzna struktura pinów ADC jest podzielona na dwie części. Piny ADC4 i ADC5 są zasilane z pinu Vcc ponieważ mają także funkcjonalność związaną z interfejsem TWI. Pozostałe piny są zasilane z AVcc.
W takim przypadku do generatora losowego warto jest wykorzystać jeden z pinów ADC4 lub ADC5 i w ten sposób dodatkowo zminimalizować ewentualny problem, który poruszyłeś w pytaniu.
Wykorzystałem Twoje pytanie do napisania artykułu: Zmieniaj funkcje pinów w trakcie pracy programu!
UsuńDzięki.
OdpowiedzUsuńMasz rację z braku doświadczenia nie wpadłem, że można to tak rozwiązać.
Przyznam, że bez Twojej uwagi o tym, że piny ADC 4-5 są zasilane z Vcc nie zauważył bym tego w datasheet'cie.
... i tutaj właśnie ujawnia się nasza rola, by wskazać, co i gdzie w dokumentacji pisze :-)
UsuńCzy wystarczy pin ADC zostawić niepodłączony czy może dolutować do niego kawałek zaizolowanego przewodu (antenkę), żeby lepiej szumiało?
OdpowiedzUsuńWystarczy zostawić niepodłączony, ale można przewidzieć jakąś krótką ścieżkę na płytce PCB, nieotoczoną polygonem (rozlaną masą).
UsuńJestem totalnie świeży, jeśli chodzi o C
OdpowiedzUsuńW podanym przykładzie
"volatile char liczba_RND; //liczba losowa dostępna zawsze i wszędzie "
Otrzymam liczbę z zakresu -128 do 127 tak?
A jeśli potrzebuję liczby z zakresu 0 do 255 to wystarczy zmienić na unsigned char lub uint8_t?
Tak, wystarczy zmienić na unsigned char lub inaczej uint8_t.
UsuńTaka mała dygresja, wiem, że wątek stary i raczej już nikt do nie będzie zaglądał, ale... O ile najmłodszy bit ADC możemy traktować jak czynnik losowy, o tyle sam generator ma pewną wadę. W prawdziwym losowym generatorze każda wartość powinna mieć takie samo prawdopodobieństwo wystąpienia (nawet pod rząd). W przypadku takiego generowania liczby o wartości: 0b01010101 i 0b10101010 mają największe prawdopodobieństwo (skoro 0 i 1 mają takie samo prawdopodobieństwo) a liczby 0b00000000 oraz 0b1111111 najmniejsze. To znaczy, że jeśli zrobimy elektroniczna kostkę do gry, to 1 i 6 mozemy się nigdy nie doczekać zaś 3 i 4 będa wypadały prawie za każdym razem (taka mała dygresja) :P
OdpowiedzUsuńTo co napisałeś nie jest prawdą. Kolejne stany najmłodszego bitu ADC są losowe i są zdarzeniami niezależnymi. Z tego można łatwo udowodnić, że uzyskanie 0, jak i 255, jak i dowolnej innej liczby 8 bitowej jest takie samo i wynosi 0,5^8.
UsuńWitam,
OdpowiedzUsuńMam pytanie odnośnie "prostego zewnętrznego generatora szumów". Czy ja dobrze widzę i kondensator ceramiczny użyty do budowy układu ma mieć wartość 100mF, czy coś przeoczyłem? Wydaje mi się to aż nie prawdopodobne aby do tak prostego układu pakować taką kobyłę :).
Pozdrawiam.