Witam!
Dzisiaj zaprezentuję kolejny projekt stworzony na mikrokontrolerze AVR i zaprogramowany w C.
Tym razem jest to generator liczb losowych na mikrokontrolerze Atmega328p.
Generalnie celem projektu jest generowanie :
a) 15 cyfrowej liczby całkowitej
b) 14 cyfrowej liczby niecałkowitej ( 7 liczb przed przecinkiem i 7 po )
Dodatkowo generator musi generować liczby w oparciu o losowe lub zbliżone do losowości źródło entropii.
Nie jest to oczywiście urządzenie kryptograficzne ale postanowiłem się postarać w tej kwestii.
Lista części
- Atmega 328p-PU
- 4 przyciski typu tact-switch
- 1 potencjometr 10 K
- 1 wyświetlacz LCD (ze sterownikiem kompatybilnym z HD44780)
- 1 rezonator kwarcowy 8 MHz
- 2 kondensatory 22 pF
- 6 kondensatorów 100 nF
- 1 dławik 100 uH
- 1 kondensator 47uF
- 2 kondensatory 100 uF
- 1 stabilizator L7805CV
- 1 odbiornik radiowy ( jako źródło losowości) ( ja użyłem odbiornika z tego zestawu : https://botland.com.pl/moduly-radiowe/3191-modul-radiowy-nadajnik-fs100a-odbiornik-433-mhz.html?search_query=FS100A&results=2)
- Płytka stykowa/ kable stykowe
- Źródło zasilania 9V.
- Oprogramowanie do wgrania programu do mikrokontrolera i ustawienia fusebitów (np. avrdude).
- Programator usbasp lub inny
Do artykułu dołączony jest skompilowany plik .hex.
W przypadku chęci własnoręcznego skompilowania kodu potrzebny będzie edytor IDE, kompilator i parę innych rzeczy. Szczegóły na ten temat można znaleźć w artykule 5 prostych projektów AVR C część 1.
Schemat
Schemat całego projektu dzieli się na 4 bloki
- Zasilanie
- Mikrokontroler
- Przyciski
- Wyświetlacz LCD
Zasilanie :
Zasilane składa się z regulatora liniowego L7805CV oraz 2 kondesatorów po 100 uF. Jeden jest na wejściu i jeden na wyjściu. Regulator przyjmuje napięcie z zakresu 7 V – 30 V i zamienia je w stabilne 5V zasilające układ.
Notatka: Przy wyższym napięciu zasilania może być potrzebny radiator w celu zwiększenia rozpraszania ciepła z regulatora. Przy 9V regulator jest ciepły. Jednak przy wyższym napięciu może zrobić się gorący.
Podblokiem zasilania jest “zasilanie mikrokontrolera”. Jest to dodatkowy filtr składający się z kondesatora 47uF oraz dławika 100 uF. Jego celem jest filtracja zasilania bloku mikrokontrolera.
Mikrokontroler :
Najbardziej skomplikowany i jednocześnie najważniejszy blok.
Sercem układu jest mikrokontroler Atmega 328p, taktowany z zewnętrznego oscylatora kwarcowego 8 MHz.
Zasilanie do AVCC i VCC pochodzi z dodatkowo filtrowanej szyny dVCC. Dodatkowo na każdym pinie zasilającym znajduje się kondensator 100 nF.
Pin Reset jest wyposażony w kondensator 100 nF ( zgodnie z dokumentacją, jest on wyposażony w wewnętrzny rezystor podciągający 10 K). Podłączyłem do niego też przycisk który zwiera go do masy resetując mikrokontroler.
W skład bloku mikrokontrolera wchodzi odbiornik radiowy będący źródłem entropii dla generatora liczb. Jest to prosty odbiornik na częstotliwość 430 MHz. Co istotne nie jest on wyposażony w dekoder więc na jego wyjściu cały czas powstaje losowy szum cyfrowy :
Sygnał ten jest wykorzystany do taktowania wbudowanego w mikrokontroler timera 1. Powoduje to, że licznik tego timera produkuje losową 16 bitową liczbę całkowitą która następnie jest wykorzystywana jako ziarno dla generatora liczb losowych.
W skład bloku mikrokontrolera wchodzi też brzęczyk, użyty do pikania. Brzęczyk nie ma wbudowanego generatora. Co jest istotne bo pozwala wygenerować miły dla ucha dźwięk który nie będzie brzmiał jak alarm przeciwpożarowy.
W skład tego bloku wchodzą również kondensatory filtrujące dla przycisków. Każdy z przycisków jest wyposażony w kondensator 100nF co dość skutecznie zabezpiecza przed drganiami styków.
Kolejnym istotnym blokiem jest wyświetlacz LCD.
Wyświetlacz którego użyłem to JHD162A-B-W. Ma on dwie linijki po 16 znaków każda.
Jednak w tym projekcie powinien zadziałać każdy wyświetlacz zgodny ze sterownikiem HD44780.
Podłączenie jest wykonanie w trybie 4 bitowym co pozwala oszczędzać piny cyfrowe mikrokontrolera.
Podłączone są tylko piny RS, E oraz 4 ostatnie piny danych. Do pinu regulującego kontrast podłączony jest potencjometr. Kręcąc nim możemy regulować kontrast. (:
Ponieważ wyświetlacz jest niebiesko biały, podświetlenie jest włączone na stałe. Inaczej nie było by na nim nic widać.
Ostatnim i najprostszym blokiem są przyciski.
Układ wyposażony w 4 przyciski (w tym 3 kontrolowane przez program).
- Reset – Resetuje układ.
- Up – Przechodzi do wyższej pozycji w menu.
- Down – przechodzi do niższej pozycji w menu.
- Enter – Uruchamia wybrane losowanie.
Oba przyciski są podłączone między pinem cyfrowym mikrokontrolera oraz masą układu.
Więc kiedy są wciśnięte odpowiedni pin cyfrowy jest zwierany do masy.
Kiedy nie są wciśnięte, na pinie sterującym panuje stan wysoki zagwarantowany przez uruchomione programowo, wewnętrzne rezystory podciągające.
Ponieważ układ ten został wykonany w celu ćwiczenia języka C, postanowiłem nie wytrawiać dla niego płytki drukowanej. Całość jest zmontowana na płytce prototypowej.
Fusebity które ustawiłem to :
1 2 3 4 5 6 |
0xF7 - Lfuse 0xDF - Hfuse 0xFD - Efuse -------Argument do AVRDUDE : -------- -U lfuse:w:0xf7:m -U hfuse:w:0xdf:m -U efuse:w:0xfd:m |
Program
Notatka : Cały projekt został zaprogramowany w języku C i skompilowany za pomocą kompilatora GCC AVR. W załączniku znajduje się archiwum zawierające skompilowany, gotowy do wgrania program oraz projekt code::blocks ( 16.02).
Cały projekt składa się z 5 plików + zewnętrznej biblioteki do obsługi wyświetlacza ( http://radzio.dxp.pl/hd44780/hd44780_avr_4-bit_norw_c.htm)
Pliki wchodzące w skład projektu :
- main.c – Plik główny
- menu.c – Plik zawierający kod źródłowy do wyświetlania różnych rzeczy oraz do menu.
- random_gen.c – Plik zawierający kod źródłowy do generowania liczb losowych.
- menu.h – Plik nagłówkowy do menu.c
- random_gen.h – Plik nagłówkowy do random_gen.c
Pliki z biblioteki HD44780.h z radzio.dxp.pl
- HD44780.h – Plik nagłówkowy biblioteki.
- HD44780.c – Plik z kodem źródłowym biblioteki.
Oto kod źródłowy wszystkich plików projektu :
main.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
#include <avr/io.h> #include <util/delay.h> #include "HD44780.h" #include <stdbool.h> #include <avr/interrupt.h> // stworzone na potrzeby projektu #include "menu.h"// biblioteka od menu #include "random_gen.h"// biblioteka od randomowych liczb volatile unsigned char button=0;// który przycisk jest wciśnięty ? volatile bool p_flag=false;//flaga, że któryś przycisk został wciśnięty ISR(PCINT1_vect)// wektor przerwania przycisku { if(bit_is_clear(PINC,5)||bit_is_clear(PINC,4)||bit_is_clear(PINC,3)||bit_is_clear(PINC,2))// sprawdź czy któryś przycisk jest wciśnięty { p_flag=true;// ustaw flagę przycisku button =check_keypad();// sprawdź który to przycisk } } void wait()// funkcja która czeka dopóki nie zostanie wciśnięty przycisk oraz pika po jego wciśnięciu { p_flag=false;// wyzeruj flagę przycisku while(p_flag!=true){}// pętla p_flag=false;// wyzeruj flagę przycisku beep(200);// zapikaj } int main(void) { bool poz =false;// pozycja kursora menu głównego, zmienna typu bool ( 2 pozycje w menu) bool update_men=true;// update menu głównego bool ent=false;// wciśnito enter DDRC |= (1<<1);// buzzer PORTC |= (1<<5)|(1<<4)|(1<<3);// inicjalizacja przycisków // inicjalizacja przerwań PCICR |= (1<<PCIE1);// włącz przerwanie PCINT1 PCMSK1 |= (1<<PCINT10)|(1<<PCINT11)|(1<<PCINT12);// ustawm jego maskę na piny przycisków // główna flaga przerwań SREG |=(1<<7);// włącz przerwania LCD_Initalize();// włączenie wyświetlacza powitanie();// wyświetlenie powitania init_random();// włączenie generatora liczb pseudolosowych while(1) { if(p_flag==true)// sprawdzenie flagi przycisków { p_flag=false;// wyzerowanie flagi przycisków beep(100);// zapikaj ( i dodaj opóźnienie 100 ms ) switch(button)// sprawdzenie który przycisk został wciśnięty { case 1:// pierwsza pozycja poz= false; update_men=true;// flaga potrzeby update menu ustawiona break; case 2:// druga pozycja poz = true; update_men=true; break; case 3: ent=true;// flaga wciśniętego enter ustawiona break; } } if(update_men==true)// update menu głównego { update_men=false;// wyzeruj flagę update głównego menu print_menu(poz); } if(ent==true)// enter wciśnięty, losuj { ent=false;// wyzeruj flagę przycisku enter //znajdź maksymalną i minimalną wartość dla danej opcji if(poz==0)// losuj całkowitą { // wyświetl napis losowanie LCD_Clear(); LCD_WriteText("Wylosowano:"); LCD_GoTo(0,1);// idź do drugiej linijki // losowanie get_seed();// wprowadź ziarno do generatora // wylosuj znak bool sign=get_rand(0,2); // wyświetl wylosowany znak if(sign==true) { LCD_WriteText(" ");// liczba dodatnia } else { LCD_WriteText("-");// liczba ujemna } for(unsigned char a=0;a<15;a++)// losuj 15 liczb { print_num(get_rand(0,10));// wyświetl je _delay_ms(300);// dla efektu :p } wait();// zaczekaj aż zostanie wciśniętu guzik } if(poz==1) // losuj rzeczywistą { // wyświetl napis LCD_Clear(); LCD_WriteText("Wylosowano:"); LCD_GoTo(0,1); // wyświetl wynik get_seed();// wprowadź ziarno do generatora // wylosuj znak bool sign=get_rand(0,2); // wyświetl wylosowany znak if(sign==true) { LCD_WriteText(" "); } else { LCD_WriteText("-"); } // pierwsze 7 znaków for(unsigned char a=0;a<7;a++) { print_num(get_rand(0,10)); _delay_ms(300);// dla efektu :p } LCD_WriteText(".");// przecinek _delay_ms(300); // następne 7 znaków for(unsigned char a=0;a<7;a++) { print_num(get_rand(0,10)); _delay_ms(300);// dla efektu :p } wait();// zaczekaj } update_men=true;// zaznacz, że trzeba wyświetlić menu } } return 0; } |
menu.h
1 2 3 4 5 6 7 8 9 10 11 12 |
#ifndef MENU_H_INCLUDED #define MENU_H_INCLUDED #include <stdbool.h>// dla zmiennej typu bool void powitanie(); void print_menu(bool opt); void print_num(unsigned char num); unsigned char check_keypad(); void beep(unsigned int time); #endif // MENU_H_INCLUDED |
menu.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
#include "menu.h" #include "HD44780.h" void powitanie()// funkcja generująca powitalny ekran { LCD_Clear(); LCD_WriteText("Generator liczb"); LCD_GoTo(0,1); LCD_WriteText("Losowych V1.0"); _delay_ms(2000); LCD_Clear(); LCD_WriteText("(C) Bob "); LCD_GoTo(0,1); LCD_WriteText("Majsterkowo 2017"); _delay_ms(2000); beep(100); LCD_Clear(); } void print_menu(bool opt)// funkcja wyświetlająca menu // argument to pozycja kursora { LCD_Clear(); LCD_WriteText("Calkowita "); // wyświetlenie "kursora" if(opt==0) { LCD_WriteText("<"); } LCD_GoTo(0,1); LCD_WriteText("Rzeczywista "); // wyświetlenie "kursora" if(opt==1) { LCD_WriteText("<"); } } unsigned char check_keypad()// funkcja sprawdzająca klawiaturę { unsigned char but=0; if(bit_is_clear(PINC,5))//sprawdź czy pin jest w stanie niskim { but=1; } if(bit_is_clear(PINC,4)) { but=2; } if(bit_is_clear(PINC,3)) { but=3; } return but;// zwróć wartość } void beep(unsigned int time)// zapikaj { unsigned int cycles=time/2; do { PORTC |= (1<<1); _delay_ms(1); PORTC &= ~(1<<1); _delay_ms(1); cycles--; } while(cycles >1); } void print_num(unsigned char num)// wyświetl liczbę { char*text="n";// zmienna z tekstem switch (num)// ustal która to jest liczba { case 0: text="0"; break; case 1: text="1"; break; case 2: text="2"; break; case 3: text="3"; break; case 4: text="4"; break; case 5: text="5"; break; case 6: text="6"; break; case 7: text="7"; break; case 8: text="8"; break; case 9: text="9"; break; } LCD_WriteText(text);// wyświwetl tą liczbę na wyświetlaczu } |
random_gen.h
1 2 3 4 5 6 |
#ifndef RANDOM_GEN_H_INCLUDED #define RANDOM_GEN_H_INCLUDED void get_seed();// funkcja wprowadzająca ziarno do generatora void init_random();// funkcja włączająca timer 1 int get_rand(int a, int b);// funkcja losująca zmienną całkowitą #endif // RANDOM_GEN_H_INCLUDED |
random_gen.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
#include "random_gen.h" #include <avr/io.h> #include <stdlib.h> // dla randomowych liczb void init_random() { TCCR1B |= (1<<CS10)|(1<<CS11)|(1<<CS12);// ustaw timer na taktowanie z zewnętrznego źródła } void get_seed()// uzyskaj losową liczbę z wejścia generatora { unsigned int seed;// zmienna przechowująca ziarno generatora seed=TCNT1;//ziarno to stan timera 1 srand(seed);// wprowadź ziarno do generatora } int get_rand(int a, int b) { int num;//liczba typu int ( bardziej uniwersalna funkcja num=(rand() % b) + a;// wylosuj liczbę z zakresu <a;b) return num;// zwróć liczbę } |
Dodatkowo zmodyfikowałem funkcję odpowiedzialną za czyszczenie wyświetlacza z zewnętrznej biblioteki.
Zmieniłem (w pliku HD44780.c):
1 2 3 4 5 |
void LCD_Clear(void) { LCD_WriteCommand(HD44780_CLEAR); _delay_ms(4); } |
Na:
1 2 3 4 5 6 |
void LCD_Clear(void) { LCD_Home(); LCD_WriteCommand(HD44780_CLEAR); _delay_ms(10); } |
Teraz działa lepiej z moim wyświetlaczem.
Poprzednio występowały błędy związane z czyszczeniem tylko jednej linijki
Dodatkowo modyfikacji wymagają definicje portów i pinów w pliku HD44780.h :
Poprawne definicje dla tego podłączenie :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#define LCD_RS_DIR DDRD #define LCD_RS_PORT PORTD #define LCD_RS (1 << 0) #define LCD_E_DIR DDRD #define LCD_E_PORT PORTD #define LCD_E (1 << 1) #define LCD_DB4_DIR DDRD #define LCD_DB4_PORT PORTD #define LCD_DB4 (1 << 2) #define LCD_DB5_DIR DDRD #define LCD_DB5_PORT PORTD #define LCD_DB5 (1 << 3) #define LCD_DB6_DIR DDRD #define LCD_DB6_PORT PORTD #define LCD_DB6 (1 << 4) #define LCD_DB7_DIR DDRD #define LCD_DB7_PORT PORTD #define LCD_DB7 (1 << 6) |
Kody są opatrzone dużą ilością komentarzy więc zamieszczę tu tylko opis tworzenie losowej liczby:
W generowaniu liczby losowej, układ bazuje na funkcji rand() z biblioteki stdlib.h .
Funkcja ta bazuje na losowym “ziarnie” dostarczanym za pomocą funkcji srand().
Ziarnem jest 16 bitowa liczba typu unsigned int .
Ziarno jest generowane w oparciu o losowy szum cyfrowy generowany na podstawie szumu radiowego.
Ten losowy szum jest użyty jako sygnał zegarowy dla timera/licznika 1 ( timer jest 16 bitowy). Na każdym zboczu wznoszącym sygnału, licznik zwiększa swoją wartość o 1. Kiedy licznik się przepełnia (przy wartości 4294967295), jego wartość jest automatycznie zerowana.
Ponieważ sygnał wejściowy jest losowy (oparty na szumie radiowym) więc i wartość tej liczby jest losowa co powoduje, że dane wyjściowe z funkcji rand() również są losowe, a przynajmniej bardzo zbliżone do losowych.
Program nie losuje jednak całej liczby na raz.
Każda cyfra jest losowana osobno (choć przy użyciu tego samego ziarna), a następnie wyświetlana na wyświetlaczu. Znak jest również losowany osobno.
I tak to generalnie działa.
Reszta w komentarzach od programu (:
Działanie
Teraz mała galeria na temat działania urządzenia :
Po włączeniu przywita nas taki oto ekran powitalny:
A następnie pokarze się główne menu :
Używając przycisków można wybrać jeden z dwóch trybów losowania :
- Losowanie 15 cyfrowej liczby całkowitej.
- Losowanie 14 cyfrowej liczby rzeczywistej ( 7 cyfr przed przecinkiem i 7 po ).
Aktualnie wybrana opcja jest sygnalizowana znakiem “<“.
Po wciśnięciu enter, program wylosuje liczbę, która będzie następnie wyświetlona na wyświetlaczu.
Liczba całkowita :
Liczba rzeczywista :
Akurat miałem szczęście i kiedy robiłem zdjęcia, obie liczby wyszły ujemne. Kiedy liczba jest dodatnia to zamiast “-“jest pusta przerwa.
Zdjęcie układu w całości :
Kilka słów na koniec
W załączniku znajduje się archiwum zawierające cały projekt codeblocks wraz ze wszystkimi kodami źródłowymi oraz skompilowanym plikiem .hex.
Plik ten znajduje się w folderze bin/release.
Projekt zawiera również zmodyfikowaną bibliotekę do obsługi wyświetlacza.
No i to tyle (:
Jak zwykle, komentarze pozytywne i negatywne mile widziane (:
Patrząc na sygnał z oscyloskopu, losowy szum cyfrowy jest wybitnie nielosowy :-)
Rzeczywiście
Najprawdopodobniej odbiornik łapie jakieś losowe transmisje cyfrowe na paśmie 430 MHz i to jest przyczyną regularności w odczycie.
Myślę jednak, że po pobraniu większej próbki sygnału, jego entropia okazałaby się znacznie wyższa niż jest widoczna na screenie z oscyloskopu.
Ale oczywiście mogę się mylić bo nie znam schematu odbiornika.
Licz po jednym bicie tak jak w generatorach opartych na liczniku Geigera.
Liczysz impulsy w jakiejś jednostce czasu, potem zerujesz licznik i liczysz drugi raz. Jeśli pierwsza ilość jest większa to bit jest jedynką, jeśli jest mniejsza to zerem, jeśli są równe to powtarzasz oba odczyty. I tak n razy dla n-bitowej liczby.
To tak w uproszczeniu.
W Geigerach liczy się po prostu czas między dwoma kolejnymi impulsami, ale w przypadku rozpadu atomowego impulsy są nieco bardziej losowe niż w przypadku radia…
To mam jeszcze kilka pytań:
– na ile Twój generator jest lepszy od wbudowanej w Arduino funkcji random()?
– czy badałeś/analizowałeś “jakość” wygenerowanych liczb? Tzn rozkład, wartości średnie, powtarzalność?
– czy Twoim zdaniem nie byłoby lepiej zamiast odbiornika radiowego, za pomocą którego generowana jest liczba, a więc potencjalnie można “sterować” sygnałem z niego otrzymanym, pokusić się o inne, bardziej “niezależne” źródło? Choćby antenka podpięta pod ADC1, cewka pod ADC2, millis() i odczyt ostatniej cyfry z każdej tych wartości?
1) Funkcja random() jest na podobnym poziomie co mój generator. Jedyną zaletą mojego generatora jest to, że jest napisany w C więc zajmuje mniej miejsca (coś koło 2-3 KB ).
2) Nie testowałem go pod tym kątem ):
Rozważałem wykorzystanie Quick Check https://hackage.haskell.org/package/QuickCheck ale
musiałbym wygenerować większą próbkę co byłoby trudne.
3) Możliwe, że byłoby to lepsze rozwiązanie ze względu na brak konieczności wykorzystania timera.
W sumie jak teraz sobie myślę to nie jest to najlepsze rozwiązanie, najlepiej byłoby wykorzystać schemat opisywany tutaj https://blog.cryptographyengineering.com/2012/02/21/random-number-generation-illustrated/
Podłączyć taki układ do pinu cyfrowego i a następnie odczytywać stan pinu i zapisywać go do jako kolejne bity danej zmiennej.
Wtedy można by równieź było użyć większej liczby takich generatorów aby odczytywać kilka bitów na raz co przyspieszyłoby cały proces.
Tak generalnie to był kolejny projekt zrobiony w celu ćwiczenia programowania AVR w C