Skocz do zawartości

Elvis

Użytkownicy
  • Zawartość

    2613
  • Rejestracja

  • Ostatnio

  • Wygrane dni

    195

Wszystko napisane przez Elvis

  1. Zapraszam do lektury artykułu: https://www.forbot.pl/forum/topics20/linux-kamerka-robot-z-ukladem-wizyjnym-vt3984.htm#31622 Jest to niejako ciąg dalszy testu. Tym razem opis podłączenia kamerki.
  2. Wszyscy przyzwyczaili się do światłolubów, ale jak zrobić robota który rozróżnia kolory, a może nawet kształty. Ostatnio na forum pojawił się bardzo aktywny wątek o bibliotece OpenCV. Pozwala ona na przetwarzanie obrazów, pozostaje jednak pytanie, jak zbudować robota sterowanego obrazem. Pierwsza myśl to kamerka WiFi. Jest to rozwiązanie proste, ale nie autonomiczne. Obraz przesyłany jest do komputera PC, sam robot nie jest w stanie odczytać obrazu. Można oczywiście wmontować PC do robota. Istnieje też nieco inna możliwość - wykorzystanie modułu z linux-em na pokładzie. MMnet1001 wydaje się wręcz idealny. Postaram się więc opisać jak z modułu MMnet1001 oraz kamerki internetowej zbudować robota. Na początek podłączamy moduł MMnet1001 i kamerkę Logitech do płytki ewaluacyjnej (EVBmm). Fizyczne podłączenie jest łatwe - na płytce jest gniazdo USB, wystarczy podłączyć kamerkę. Wśród dostarczonych z modułem pakietów znajduje się uvc_streamer - program pozwalający na umieszczenie obrazu z kamery w internecie. Co ciekawe program nazywa się MJPG-streamer, projekt dostępny jest tutaj: http://sourceforge.net/projects/mjpg-streamer/develop Na początek sprawdzimy, czy kamera działa. Szukamy urządzenia /dev/video i okazuje się, że system kamerki nie widzi. Konieczne jest ręczne wczytanie odpowiednich modułów (w odpowiedniej kolejności). Wykonujemy następujące komendy: insmod v4l1-compat insmod videodev insmod gspca_main insmod gspca_zc3xx Jeśli okaże się, że opisywanych modułów nie ma w naszym systemie, konieczna jest rekompilacja jądra. Po przeczytaniu dokumentacji modułu, nie powinno to stanowić problemu. Zajmuje ok. godzinki i lepiej nie używać wersji IDE z maszyny wirtualnej. Gdy moduły zostaną załadowane, po podłączeniu kamerki pojawi się urządzenie /dev/video0. Teraz możemy uruchomić uvc_streamer: Aby zobaczyć obraz z kamery, łączymy się za pomocą przeglądarki z portem 8080 naszego modułu: Jak widać obraz jest całkiem niezłej jakości, mamy już to co oferuje nam kamerka po WiFi - podgląd obrazu. Czas na odczytanie obrazu z kamerki. Pod Linux-em do komunikacji z kamerką wykorzystamy moduł Video4Linux. W internecie znajdziemy sporo informacji, dokumentacji i przykładowych programów. Warto na początek uruchomić program na PC, a dopiero później próbować sił na robocie. Ja wykorzystałem źródła programu MJPG-streamer. Dostępny jest on na licencji GNU, więc i mój program obejmuje ta licencja. Wykorzystując gotowe moduły odczyt danych z kamery jest bardzo łatwy. Właściwie 2 linijki kodu wystarczą do odczytu obrazu z kamery. Inicjalizacja kamery i ustawienie parametrów: video_dev = init_videoIn(&videoIn, device, 320, 240, 1, V4L2_PIX_FMT_MJPEG, 1); Oraz sam odczyt obrazu: uvcGrab(&videoIn) Obraz pobieramy w formacie JPEG, możemy zapisać go do pliku, aby sprawdzić, czy wszystko działa. Poniżej obrazek pobrany z kamerki: W programie możemy wybrać rozdzielczość oraz liczbę klatek na sekundę. Oczywiście w granicach tego, co oferuje sprzęt. Dalej pojawiają się niestety problemy. Moja kamerka za nic nie chce przyjąć innej konfiguracji niż wysyłanie obrazów w formacie JPEG. Pozornie nie ma z tym problemu, jednak odkodowywanie obrazów będzie zajmowało dużo czasu. Widać już, że zwykła kamerka internetowa nie najlepiej się do budowy robota nadaje. Należałoby poszukać kamery, która jest w stanie przesłać nieskompresowany obraz. Z braku lepszej kamery pozostaje odkodowanie obrazu zapisanego w pliku JPEG. Żeby nie pisać samemu dekodera jpeg-ów, pobieramy gotową bibliotekę. Ja poprałem ją stąd: http://heanet.dl.sourceforge.net/project/libjpeg/libjpeg/6b/jpegsr6.zip Plik trzeba rozpakować, można najpierw skompilować na PC i zobaczyć jak działa, następnie przygotowujemy wersję dla ARM. Wykonujemy polecenia: ./configure CC='arm-linux-uclibc-gcc' make i mamy gotową wersję na nasz mikrokontroler. Żeby jeszcze bardziej ułatwić sobie życie, wykorzystamy gotowy program o nazwie 'djpeg'. Pozwala on na zmianę formatu pliku z jpeg, na łatwiejszy od dalszej obróbki. Do wyboru są różne formaty plików, np. bmp, gif. Najłatwiej będzie odczytać domyślny format, czyli pnm. Jeśli chcemy poznać szczegóły tego formatu plików, jego opis znajdziemy tutaj: http://netpbm.sourceforge.net/doc/pnm.html Jest to bardzo prosty format, na początku 3 linijki nagłówka, później dane obrazu zapisane jako RGB: Potrafimy już pobrać obraz z kamery, przekodować go do prostego formatu. Teraz czas na analizę obrazu. Najlepszym rozwiązaniem byłoby wykorzystanie biblioteki OpenCV. Na początek (albo raczej z braku czasu) przygotujemy program, który będzie jedynie oceniał jasność obrazu. Celem będzie przygotowanie światłoluba. Dzielimy więc obraz na 3 części i liczymy jasność każdej z nich. Na początek przygotowujemy zdjęcia testowe. Odpowiednio dla jazdy w lewo, prosto i w prawo. Następnie przygotowujemy prosty program. Na podstawie zdjęcia będzie on wydawał polecenie odpowiedzialne za kierunek jazdy. Możliwe polecenia to: LEFT CENTER RIGHT Testujemy program na mikrokontrolerze i przykładowych obrazkach: Po przetestowaniu programu na przykładowych zdjęciach czas uruchomić wszystkie programy na mikrokontrolerze. Za pomocą skryptu cyklicznie będziemy uruchamiać: program od odczytu obrazu z kamery dekoder JPEG do PNM program decydujący o kierunku jazdy Niestety czas działania jest bardzo słaby. Poleceniem time możemy sprawdzić, który program zajmuje nam tak dużo czasu. v4l - program do odczytu danych z kamery. Wykonanie go to aż 2.64s. djpeg - dekoder jpeg do pnm. Działa nawet szybko, 310ms pictest - program decydujący o kierunku jazdy. 290ms. Wniosek jest prosty: nie można uruchamiać programów kolejno, trzeba przygotować jeden, który będzie na bierząco pobierał dane z kamerki i poddawał obróbce. Drugi wniosek jest mniej przyjemny - nawet gdyby pobieranie obrazu trwało zero sekund, to i tak pozostaje 600ms na klatkę. To baaardzo mało. Na pewno używając kamerki z JPEG nie uzyskamy pełnego realtime-u. Ale może dla światłoluba wystarczy. Optymalizacja Uruchamianie programów przez skrypt dało bardzo kiepskie rezultaty. Najwięcej zajmuje inicjalizacja kamery (ponad 2 sekundy). Aby przyspieszyć działanie konieczne jest połączenie programów do odczytu danych, dekodowania oraz podejmowania decyzji w jeden. Poniżej widać rezultat działania poprawionego programu: Tym razem odczyt z kamery prawie nie zajmuje czasu (<= 1ms), najwięcej zajmuje dekodowanie JPEG-ów. Co prawda kod nie jest optymalny - za każdym razem tworzony jest nowy proces i uruchamiany program djpeg, jednak nawet po zmianie kodu ten fragment zabierałby cenny czas. Całe przetwarzanie zajmuje ok. 280 ms. Kolejnym krokiem będzie wykorzystanie kamery do sterowania robotem. Robot sterowany jest przez dwa procesory. Na płytce ewaluacyjnej znajduje się opisywany wcześniej ARM9 z Linux-em na pokładzie. Procesor dokonuje analizy obrazu z kamerki (bardzo prostym algorytmem) i przekazuje dane o kierunku jazdy do drugiego procesora. ATMega16 odpowiedzialna jest za sterowanie silnikami. Do wymiany informacji między procesorami wykorzystałem UART (buforowany, aby zapewnić konwersję napięć z 5V na 3.3V). Wnioski Wykorzystana kamerka zupełnie nie spełniła oczekiwań. Dekodowanie JPEG-ów zajmuje bardzo dużo czasu, znacznie więcej niż algorytm analizy obrazu. W rezultacie robot reaguje dość wolno na zmianę kierunku oświetlania, zwykły światłolub bez mikrokontrolera działa o wiele sprawniej. Kolejnym problemem okazał się algorytm analizy obrazu. Wybrany jako prosty przykład, działał dla obrazów testowych, jednak w prawdziwym robocie zupełnie się nie sprawdził. Tylko dla dobrze ustawionego źródła światła potrafił za nim podążać. Robot bazuje na dwóch płytkach ewaluacyjnych. Budując "prawdziwego" robota należałoby oczywiście wykorzystać mniejszą płytkę specjalnie zaprojektowaną. Testowy robot praktycznie nie korzystał z możliwości podrzędnego mikrokontrolera. AVR jedynie wykonywał polecenia układu ARM9. Do zadań krytycznych czasowo należałoby wykorzystać możliwości mniejszego układu. Robot powinien posiadać dodatkowe czujniki, np. odległości lub linii i za ich pomocą dokonywać natychmiastowej oceny otoczenia. Analiza obrazu może służyć jedynie jako pewna "podpowiedź" gdzie robot powinien się kierować, albo jakim algorytmem posługiwać. Przykładowo robot typu LF mógłby na podstawie analizy obrazu dobierać prędkość jazdy, jednak samo wykrywanie linii musiałoby odbywać się tradycyjnymi metodami. Wykorzystanie gotowego systemu operacyjnego znacznie ułatwia obsługę urządzeń opartych na łączu USB. Podłączenie kamerki nie było oczywiste, jednak znając metodę zajmuje 15 minut. Otwarty kod (Open Source )pozwala na łatwe opracowywanie własnych programów. Programując można najpierw przetestować działanie na komputerze PC z zainstalowanym Linux-em, a gdy program zacznie działać wystarczy przekompilować na ARM9. Niestety budowa robota rozpoznającego obraz nie jest łatwa. Wymaga dobrej znajomości linux-a, komunikacji oraz przygotowania algorytmów rozpoznawania obrazu. Opisywana platforma to jedynie wstęp do budowy prawdziwego robota. Jest to bardzo ciekawy temat, ale ze względu na poziom trudności polecam jako pracę magisterską, jeśli nie temat doktoratu. Zmiana kamerki Po pierwszych testach okazało się, że kamerka Logitech C200 zupełnie nie spełniła oczekiwań. Dekodowanie JPEG do mapy bitowej zajmowało o wiele za dużo czasu. Kupiłem inną kamerkę. Tym razem wybrałem kamerkę firmy Modecom - model chyba "Yoyo Web Cam". Na pudełku była informacja, że kamera obsługuje format pikseli RGB24. Parametry jej są przeciętne, ale nie o to chodziło. Więcej informacji na stronie: http://www.modecom.pl/product.php?category=18&id=685&lang=pl&arch=0 Kamerka obsługuje standard UVC, więc podłączenie do linuxa było bardzo proste. Na stacjonarnym komputerze obraz w 640x480 pikseli jest dość dobrej jakości. Kolejnym krokiem było napisanie programu, który pobierze dane z kamery. Tutaj pojawiła się pierwsza niespodzianka. Kamerka wcale nie pracuje w trybie RGB24! Zamiast tego używa YUYV. Nie jest to idealne kodowanie, ale zawsze lepiej niż JPEG. Po szybkim przeczytaniu kilku artykułów o kodowaniach YUV, przygotowałem program v4lyuv - kod w załączniku. Linki do stron z których korzystałem: http://www.fourcc.org/fccyvrgb.php http://en.wikipedia.org/wiki/YUV Program pobiera 5 obrazów w rozdzielczości 320x240 i dekoduje je trzema sposobami. Pierwsze dekodowanie zamienia obraz na RGB, działa na liczbach zmiennopozycyjnych. Drugi sposób dekodowania działa na liczbach całkowitych. Trzecie kodowanie wykorzystuje tylko składową Y, czyli jasność koloru. Poniżej czasy przetwarzania na komputerze PC: Kolejnym krokiem było przekompilowanie kodu na ARM. Co ciekawe musiałem zmienić sposób kodowania pikseli. Ustawienie YUYV działa na PC, ale na ARM zgłasza błąd. Po zmianie na MJPEG działa pięknie i co najciekawsze nadal zwraca obraz w formacie YUYV. W załączniku kod programu dla ARM - v4lyuvarm. Poniżej czas działania: Czas przetwarzania, szczególnie trzecim algorytmem jest o wiele krótszy niż z poprzednią kamerką. Wynosi ok. 90ms i można go jeszcze skrócić analizując tylko fragment obrazu. Kod nie jest zoptymalizowany, można byłoby pewnie sporo przyspieszyć działania. W ostatecznej wersji przydałoby się zakodować to w asemblerze. Poniżej przykładowe obrazki: Wynik działania trzeciego algorytmu - tylko składowa jasności: Nieoczekiwany koniec artykułu Planowałem zastosowanie nowej kamery do sterowania robotem testowanym poprzednio. Szybsze przetwarzanie obrazu oraz gotowa informacja o jasności dawała nadzieję na lepsze rezultaty. Niestety nastąpił mały wypadek. Akumulator Li-Pol z nieznanych mi zupełnie przyczyn uległ samozapłonowi. Na szczęście płyta z linux-em na pokładzie przetrwała, ale płytka z atmegą oraz cały napęd robota uległ zniszczeniu. Poniżej zdjęcia ku przestrodze. OSTROŻNIE A AKUMULATORAMI LITOWYMI CamRobot.zip v4larm2.zip v4lyuvarm.zip v4lyuv.zip
  3. Wszystko jest opisane w artykule. W załącznikach jest przykładowy program, a w linkach odnośnik do stron z tutorialami.
  4. Moim zdaniem wychodzi 70us, ale to chyba nie robi różnicy. Ogólnie powinno działać. Natomiast aktualizacja zmiennych C0 itd. będzie powodować błędy. Czasem pojawią się impulsy o długości ponad 20ms. Zmiana zmiennych C0 itd. jest "bezpieczna" tylko podczas zerowania licznika głównego. Dlatego stosuje się dodatkowe zmienne, tzw. cienie, które są kopiowane podczas zerowania.
  5. Chyba będzie miał 70us, ale nie jestem pewien. Najlepiej oscyloskop podłączyć i sprawdzić.
  6. To chyba łatwiej napisać funkcję, która przeliczy kąt na wartość PWM niż modyfikować działanie PWM. Możesz nawet dodać takie przeliczanie przy resetowaniu licznika, będziesz miał od razu z głowy rejestry cienie (shadow registers). Bo bez tego serwa mogą szarpać.
  7. Coś przekombinowujesz. Mój program na Cn x 10µs wystawia logiczne 1 na wyjściu. Czyli jak chcesz obrócić w jedną stronę wykonujesz kod: C0 = 6; I dostajesz impuls 60us. Jak potrzebujesz w przeciwną stronę, to piszesz: C0 = 240; I impuls wynosi 2.4ms. [ Dodano: 10 Sie 10 02:56 ] a co do zerowania, to: static int counter=0; Będzie działać poprawnie. Zmienna jest zadeklarowana jako statyczna, więc przypisanie zera zostanie wykonane tylko raz, przy pierwszym wejściu w zasięg zmiennej.
  8. W Twoim rozwiązaniu trzymasz procesor w pętli podczas przerwania. To bardzo zły pomysł pod prawie każdym względem. Zamiast tego wywołuj przerwanie co 10µs (lepiej trochę rzadziej). I w tym wywołaniu sprawdzaj, czy liczniki PWM wymagają zmiany stanu - przykład znajdziesz w moim artykule. Ja używam przerwań co 100µs, ale to można oczywiście zmienić. [ Dodano: 10 Sie 10 02:19 ] Coś pomieszane jest w zmiennych, ja bym to napisał tak: SIGNAL(SIG_OUTPUT_COMPARE1A)//Przerwanie obsługi serw) { static unsigned int counter = 0; if (++counter>=2000) { // 20ms / 10µs counter = 0; PORTC = 0xff; } if (C0==counter) {PORTC &= 0b11111110;} if (C1==counter) {PORTC &= 0b11111101;} if (C2==counter) {PORTC &= 0b11111011;} if (C3==counter) {PORTC &= 0b11110111;} if (C4==counter) {PORTC &= 0b11101111;} if (C5==counter) {PORTC &= 0b11011111;} if (C6==counter) {PORTC &= 0b10111111;} if (C7==counter) {PORTC &= 0b01111111;} }
  9. Zobacz moje artykuły o PWM: https://www.forbot.pl/forum/topics20/kurs-programowania-arm-cz7-pwm-vt3926.htm i https://www.forbot.pl/forum/topics20/kurs-programowania-arm-cz8-pwm-cd-vt3948.htm Poza początkiem, który dotyczy sprzętowego PWM i jest inny dla ARM i AVR, dalej jest dokładnie to co robisz - programowy PWM. Wystarczy że zmienisz stałą czasową na 10µs, okres PWM na 20ms i gotowe.
  10. Poprzednia lekcja Lekcja 8 - PWM ciąg dalszy Następna lekcja Rejestry cienie Poprzedni program był bardzo prosty, niestety miał kilka mankamentów. Pierwszy to problem zmiany wartości zmiennej pwm_led_0. Jeśli zmienimy wartość tej zmiennej podczas działania PWM to możemy uzyskać dość dziwne działanie - zmienimy wartość na mniejszą, a na w efekcie na chwilę dioda zapali się na bardzo długo (cały okres PWM). Wyobraźmy sobie następującą sytuację. Nasz PWM miał wypełnienie 50%, zmieniamy na 10%. Jeśli zmiany dokonamy, gdy licznik jest pomiędzy 10%, a 50% to procedura PWM nie wygasi diody! Przy następnym okresie będzie już działać poprawnie, ale jeśli często będziemy zmieniać wartość wypełnienia, nasz PWM będzie działać niepoprawnie. Problem taki pojawia się, gdy zmieniamy wypełnianie PWM podczas pracy. Najlepiej byłoby zmieniać wypełnianie dopiero po zakończeniu okresu PWM. Możemy to zrealizować tworząc rejestr cień (ang. shadow). Rejestr ten będzie przechowywał aktualnie używaną wartość PWM, po zakończeniu okresu PWM wartość zwykłego rejestru będzie kopiowana do rejestru cienia. Nowa procedura obsługi PWM wygląda następująco: void pwm_proc() { static unsigned int pwm_led_0_shadow = 0; if (++pwm_counter>=PWM_PERIOD) { pwm_led_0_shadow = pwm_led_0; pwm_counter = 0; GPIO1_IOSET = LED_0; } if (pwm_counter==pwm_led_0_shadow) GPIO1_IOCLR = LED_0; } Zmienna pwm_led_0_shadow została zadeklarowana jako static. Zachowuje się jak zmienna globalna, ale poza procedurą nie ma do niej dostępu - dzięki temu "przypadkiem" nie zmienimy jej wartości. Warto przeanalizować ten program - większość procesorów ma wbudowaną obsługę rejestrów cieni w sprzętowych modułach PWM. program12.zip zawiera przykładowy kod płynnie zapalający i wygaszający diodę. Poniżej filmik z rezultatem działania: Więcej linii PWM Gdy potrafimy już sterować jedną linią (i diodą) możemy rozbudować program o obsługę wielu (w naszym przypadku 8) diod. Moglibyśmy wykorzystać metodę „kopiuj-wklej” i 8 razy skopiować poprzedni program. Nie jest to oczywiście dobra metoda, więc my postąpimy inaczej. Wykorzystamy tablice. Procedura obsługi PWM wygląda tak: void pwm_proc() { static unsigned int pwm_counter = 0; static unsigned int pwm_led_shadow[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; int i; if (++pwm_counter>=PWM_PERIOD) { for (i=0;i<8;i++) pwm_led_shadow[i] = pwm_led[i]; pwm_counter = 0; GPIO1_IOSET = LED_MASK; } for (i=0;i<8;i++) if (pwm_counter==pwm_led_shadow[i]) GPIO1_IOCLR = LED_PIN[i]; } Od poprzedniej różni się głównie dodaniem pętli oraz wykorzystaniem tablic zamiast zmiennych. Nowy program będzie wykorzystywał wartości pwm_led[0] ... pwm_led[7] do ustawiania jasności diod D0...D7. Program program13.zip zapala i gasi na zmianę diody D0 i D1. Stała: #define LIGHT_N 20 definiuje w ilu krokach będziemy zapalać/gasić diody. Funkcja set_led_light(int value) realizuje niejako przechodzenie świecenia od D0 do D1. Dla parametru value=0 zapalana jest D0, następnie zwiększając value przygaszana jest D0, a rozjaśniana D1. Dla value=LIGHT_N w pełni świeci D1. Kod jest następujący: void set_led_light(int value) { pwm_led[0] = (LIGHT_N-value)*PWM_PERIOD/LIGHT_N; pwm_led[1] = value*PWM_PERIOD/LIGHT_N; } Działanie programu pokazuje film: W pliku program14.zip znajdziemy jeszcze jeden przykład zastosowania naszego PWM - kolejne zapalanie wszystkich 8 diod. Program jest nieco bardziej rozbudowany, diody zamiast liniowo, wygaszane i zapalane są stablicowaną funkcją sinus - takie sterowanie daje nieco ładniejszy efekt i jest bardzo ważne gdybyśmy sterowali silnik za pomocą PWM. Film z rezultatem działania: Program14.zip Program13.zip Program12.zip
  11. Jest to trzecia i ostatnia część prezentacji modułu MMnet1001. W części pierwszej opisywałem dostęp do portów I/O. Przykłady od producenta Przykładowy program bazował na przykładach dostarczonych przez producenta (Propox). Działanie programu jest nieco dziwne - do portów I/O odwołujemy się przez system plików. Nie są to prawdziwe pliki, a jedynie "wirtualne". Jest to ogólna zasada w Linuxie - do peryferiów odwołujemy się przez pliki. W pliku led_v1.c znajdziemy opisywany program. Jego działanie obrazuje oscylogram poniżej: Porównywać będziemy impulsy o poziomie 0. Jak widać aktualna wersja potrafi wygenerować impuls o szerokości 25µs, co odpowiada częstotliwości 20kHz. Jest to nieco lepiej niż w poprzednio testowanym programie (ok. 14kHz), ale nadal dużo poniżej możliwości małych mikrokontrolerów. Warto jeszcze zwrócić uwagę na jeden fragment programu - po wygenerowaniu paczki (1000) impulsów, program czeka przez pewien czas wywołując usleep(1000); . Jest to bardzo ważne - bez opóźnienia system ma problemy z działaniem. Mapowanie rejestrów Dostęp do portów I/O przez system plików jest nieprawdopodobnie wolny. Pozostaje poszukać innej metody. W linuxie nie można bezpośrednio odwoływać się do dowolnego adresu w pamięci. Można jednak wykorzystać funkcję mmap, aby bezpośrednio dostać się do rejestrów. Tą samą metodą można odwołać się do innych układów peryferyjnych mikrokontrolera, np. PWM. Na początek musimy poznać trochę procesor oraz adresy jego rejestrów. Dokumentacja dostępna jest na stronie producenta: http://www.atmel.com/dyn/products/datasheets.asp?family_id=605 Najważniejsze to ogólny opis procesora: http://www.atmel.com/dyn/resources/prod_documents/6221s.pdf Oraz dokładny opis: http://www.atmel.com/dyn/resources/prod_documents/doc6221.pdf Nie musimy teraz wszystkiego czytać, ale warto wiedzieć, że adres rejestru portu PIOA znajdziemy w pierwszym dokumencie, a wszystkie wykorzystane rejestry oraz działanie portów I/O wyjaśnione jest w drugim. Program testowy bazuje na przykładzie znalezionym w internecie - po drobnych poprawkach nawet działa. Na początek otwieramy urządzenie reprezentujące pamięć (potrzebujemy go, aby wywołać mmap): open("/dev/mem", O_RDWR | O_SYNC) Następnie wywołujemy funkcję mmap: mmap(0, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, AT91C_BASE_PIOA) Warto zwrócić uwagę na parametr AT91C_BASE_PIOA. Jest to stała zdefiniowana następująco: #define AT91C_BASE_PIOA 0xFFFFF000 Jej wartość to adres interesujących nas rejestrów. Po wywołaniu mmap dostaniemy wskaźnik, który będzie odpowiadał adresowi 0xFFFFF000 pamięci fizycznej. Za pomocą zwykłej arytmetyki wskaźników możemy się już odwoływać do fizycznych rejestrów procesora. void write_pio(unsigned int offset, const unsigned int value) { *((int*)(((char*)pin_addr) + offset)) = value; } unsigned int read_pio(unsigned int offset) { return *((int*)(((char*)pin_addr) + offset)); } Mając dostęp do rejestrów możemy już napisać funkcje sterujące liniami I/O: int pio_set_gpio_output(unsigned pin, int value) - ustawia kierunek portu (wejście lub wyjście) int pio_set_value(unsigned pin, int value) - ustawia stan linii portu int pio_get_value(unsigned pin) - odczytuje stan linii W załączonym programie led_v2.c znajdziemy pełny kod programu. Poniżej rezultat działania: Tym razem prędkość działania jest znacznie wyższa. Impuls ma szerokość poniżej 1µs (ok. 880ns), co odpowiada częstotliwości ok. 568kHz. To już lepiej, 28x szybciej niż poprzednio. Optymalizacja Okazuje się, że wywoływanie funkcji pio_set_value zajmuje sporo czasu. Zoptymalizujemy kod i będziemy bezpośrednio odwoływać się do wskaźników. Aby wyzerować linię: *pio_c0dr = 0x0200; - Żeby ustawić: *pio_s0dr = 0x0200; Teraz kod działa następująco: Jak widać szerokość impulsu spadła do 80ns, czyli uzyskaliśmy częstotliwość 6,25MHz. Wreszcie możemy pościgać się z atmegą Podsumowując, Linux nie jest przeszkodą w szybkim dostępie do układów peryferyjnych. Można mieć wielozadaniowy system operacyjny, a jednocześnie niskopoziomowy dostęp do sprzętu. led_v3.c led_v2.c led_v1.c
  12. W poprzedniej części przedstawione zostały główne funkcje modułu MMnet1001. Po teście, producent (Propox) dostarczył płytę ze skonfigurowanym środowiskiem programistycznym (IDE). Do programowania potrzebny jest odpowiednio przygotowany Linux zainstalowany na komputerze PC. Przygotowanie takiego środowiska samemu zajmuje sporo czasu, wymaga dobrej znajomości Linux-a i nieco wysiłku. Obecnie możemy dostać wszystko gotowe w postaci obrazu maszyny wirtualnej (VirtualBox). Aby rozpocząć programowanie wystarczy jedynie pobrać odpowiedni program, postępować zgodnie z instrukcją i po krótkim czasie mam gotowy wirtualny system przygotowany do pracy. Producent dostarczył przykładowy program do komunikacji przez port szeregowy, kross-kompilator (SDK) dla płytki, a co najważniejsze gotowe skrypty dla makefile. Dzięki skryptom programowanie, albo raczej uruchamianie jest o wiele łatwiejsze. Wystarczy wydać polecenie 'make', aby skompilować program i skopiować go na moduł MMnet1001. Pozostaje już tylko uruchomić program i sprawdzić jak działa. Z modułem dostarczany jest przykładowy program migający diodą na płytce. Niestety nie udało mi się uruchomić dostarczonego obrazu pod systemem Windows. W przypadku Linux-a wszystko działa pięknie. Teoretycznie tak samo powinno być pod kontrolą MS Windows. Problem pojawia się podczas uruchamiania Linux-a na emulatorze. Pojawiają się błędy dostępu do dysku, system nie startuje poprawnie. Pomimo prób naprawiania za pomocą fsck nie udało mi się uzyskać działającego IDE pod Windows. Trzeba się pogodzić z faktem, że chcąc programować pod linux-a, należy na nim pracować. Kolejną wadą rozwiązania jest zmniejszenie wydajności. O ile w przypadku kompilacji krótkich programów nie ma to znaczenia, o tyle kompilacja jądra na emulatorze wydaje się nie mieć sensu. Jednak dostarczone IDE nie jest bezużyteczne. Po pierwsze można w miarę łatwo rozpocząć tworzenie własnych programów. Na pewno o wiele łatwiej niż konfigurując wszystko samemu. Po drugie, gdy już trochę poznamy dostarczone narzędzie, możemy własnego Linuxa skonfigurować według dostarczonych przykładów. W moim przypadku skorzystałem z tej drugiej opcji. Zalety: IDE znacznie ułatwia rozpoczęcie programowania dla modułu MMnet1001 Środowisko jest gotowe do pracy, nie wymaga konfiguracji Wykorzystanie dostarczonych skryptów usprawnia pracę Wady: Nie działa emulacja pod Windows Emulowany system jest wolniejszy i zajmuje dużo zasobów komputera Podsumowując dostarczone IDE jest bardzo wygodnym narzędziem. Pozwala na łatwe rozpoczęcie przygody z Linux-em na urządzeniach MMnet100x.
  13. Poprzednia lekcja Lekcja 7 - PWM Następna lekcja Zacznijmy od omówienia wad procesorów. Są to urządzenia czysto cyfrowe. Pracują w logice zero-jedynkowej (binarnie). Oodczytują lub wypisują logiczne 0, co odpowiada zwarciu linii do masy, albo logiczne 1, czemu odpowiada podłączenie linii do zasilania (w przypadku LPC2114, 3.3V). Gdy sterujemy np. świeceniem diody LED, oznacza to że możemy albo wyłączyć diodę, albo zapalić ją z maksymalną jasnością. Nie mamy możliwości sterowania jasnością świecenia. Jak to się dzieje, że w komputerach, telefonach itd. można regulować jasność podświetlenia? Z pomocą przychodzi nam właśnie PWM. Sztuczka polega na tym, że procesor przez chwilę zapala diodę, po czym na moment ją wygasza. Gdy dzieje się to odpowiednio szybko, oko ludzkie nie jest w stanie zarejestrować migania diody i widzimy jedynie słabiej świecącą diodę. Jak sterujemy jasnością? Jeśli czas wygaszania jest dłuższy, a świecenia krótszy to dioda świeci słabiej. Podobnie, im dłuższy jest czas świecenia, tym świeci jaśniej. Dla ułatwienia przyjmuje się, suma czasu świecenia i wygaszenia diody jest stała. Czas ten dobiera się, tak aby nie było widać migotania. Następnie steruje się czasem świecenia diody - pozostały czas to wygaszenie. Czas świecenia można wyrażać w sekundach, albo co często jest łatwiejsze w procentach. Mówimy, że PWM ma wypełnienie 25%, jeśli przez 1/4 czasu dioda świeci, a przez 3/4 jest wygaszona. Oscylogram PWM o wypełnieniu 25%: Wypełnienie 25% oznacza, że sygnał ma poziom wysoki przez 25% czasu, co widać na obrazku. Podobnie dla wypełnienia 95%, tylko przez krótki czas pojawią się impulsy o niskim poziomie: Sprzętowy PWM Wiele procesorów ma wbudowane układy PWM. Wystarczy wysterować odpowiednie rejestry i na linii procesora pojawia się sygnał PWM. Najpierw omówimy jak wykorzystać sprzętowy PWM w procesorze LPC2114. Procesor ma maksymalnie 6 kanałów PWM. Mają one bardzo rozbudowane możliwości konfiguracji, każdy może pracować z innym wypełnieniem. Linie, które mogą pracować jako PWM na schemacie oznaczone są jako PWM1, PWM2 ... PWM6. Jak widać każda linia może spełniać jedną z wielu funkcji. W przypadku większości linii do dyspozycji są 4 możliwości. Przykładowy program uruchomi PWM5 na linii P0.21 procesora. Efekt działania programu był pokazany wcześniej - wypełnienie 95%: void pwm_init() { PCB_PINSEL1 |= 0x00000400; PWM_PR = 0x00000000; PWM_PCR = 0x2000; PWM_MCR = 0x0001; PWM_MR0 = 15000; PWM_MR5 = 15000 * 0.95; PWM_LER = 0x21; PWM_TCR = 0x01; PWM_TCR = 0x09; } Za pomocą rejestru PCB_PINSEL1 ustawiamy, którą funkcję będzie pełnił dany pin P0.21. Większość linii procesora może pełnić kilka funkcji. Za pomocą rejestrów PCB_PINSEL0, PCB_PINSEL1 oraz PCB_PINSEL2 ustawiamy jaką funkcje pełni dana linia. Domyślnie większość linii to zwykłe linie I/O. Kolejny interesujący rejestr to PWM_MR0. Jest to licznik dla kanału 0 oraz podstawa czasu (okres) w naszej konfiguracji. Jak pamiętamy zegar peryferiów ma częstotliwość 15MHz. Gdy wpiszemy do PWM_MR0 wartość 15000, otrzymamy częstotliwość 15Mhz/15000, czyli 1kHz. Więc okres naszego PWM będzie wynosił 1ms. To zgadza się z odczytem z oscyloskopu, który widzieliśmy wcześniej. Następny interesujący rejestr to PWM_MR5. Odpowiada on za wypełnienie dla PWM5, czyli tego, który konfigurujemy. Wpisujemy do niego wartość 14250 (15000 * 95%). Pozostałe rejestry opisane są w dokumentacji procesora. Liczba opcji i możliwości jest naprawdę spora. Niestety nasza linia nie jest podłączona do żadnego układu w płytce prototypowej. Do testów wykorzystaliśmy oscyloskop, jednak dla większości użytkowników takie rozwiązanie może nie być idealne. Gdyby jednak ktoś był zainteresowany programem przykładowym, znajduje się on w archiwum program18.zip. Programowy PWM Sprzętowy PWM jest najdokładniejszy i w wielu zastosowaniach niezastąpiony. W naszym przypadku niestety nie mamy jak sprawdzić jego działania. Pozostaje nam napisać program, który wygeneruje przebieg odpowiadający PWM. Użyjemy naszego „programowego” PWM do płynnego sterowania jasnością diod LED. Taki sam kod można wykorzystać np. do sterowania wieloma serwomechanizmami. Do utworzenia PWM wykorzystamy znany nam już Timer0. Przerwanie wywoływane jest co 100us i taki właśnie będzie miał minimalny krok nasz PWM. W pierwszej wersji przyjmiemy okres PWM bardzo długi, tak abyśmy gołym okiem mogli sprawdzić działanie programu. #define PWM_PERIOD 10000 Procedura obsługi PWM będzie wołana co 100us, czyli okres naszego PWM będzie wynosił: 100us * 10000 = 1ms * 1000 = 1s Przy każdym wywołaniu procedury PWM zwiększany będzie licznik PWM unsigned int pwm_counter; Gdy osiągnie wartość PWM_PERIOD, licznik będzie zerowany i rozpocznie się kolejny okres PWM. W zmiennej globalnej: volatile unsigned int pwm_led_0 = 0; Będziemy przechowywać wypełnienie PWM (poziom jasności diody). Warto zwrócić uwagę na słowo kluczowe volatile. Zmienna będzie wykorzystywana zarówno w przerwaniach, jak i w kodzie głównym. Bardzo ważne jest, aby kompilator unikał optymalizowania odwołań do takiej zmiennej. Na początku okresu PWM będziemy zapalać diodę D0. Gdy licznik PWM zrówna się z wartością pwm_led_0, dioda będzie gaszona. Kompletny kod procedury PWM wygląda następująco: void pwm_proc() { if (++pwm_counter>=PWM_PERIOD) { pwm_counter = 0; GPIO1_IOSET = LED_0; } if (pwm_counter==pwm_led_0) GPIO1_IOCLR = LED_0; } Gdy do zmiennej pwm_led_0 wpiszemy 5000 (czyli PWM_PERIOD/2), przez pół sekundy dioda będzie zapalona, przez kolejne pół zgaszona. Dla pwm_led=500 (czyli PWM_PERIOD/10) dioda będzie zapalać się na bardzo krótko (100ms), a przez pozostały czas pozostanie zgaszona. Warto poeksperymentować z różnymi wartościami, aby zrozumieć działanie PWM. W archiwum progam11.zip znajdziemy przykładowy program. Poniżej oscylogram dla wypełnienia 10% oraz film z rezultatem działania. "Prawdziwy" PWM Dotychczas używaliśmy PWM o okresie 1s. Taka wartość jest najczęściej za duża w prawdziwych programach. Zmienimy wartość stałej PWM_PERIOD, tak aby uzyskać sterowanie jasnością diod zamiast ich migania. Przyjmijmy okres PWM równy 20ms. Ponieważ procedura jest wołana co 100us, więc okres PWM_PERIOD powinno wynosić: 20ms / 100us=20000/100 = 200 Po zmianie kodu na: #define PWM_PERIOD 200 Warto sprawdzić działanie diody. Teraz dla pwm_led=100 (PWM_PERIOD/2) dioda będzie świecić w mniej więcej połowie jasności. Dla pwm_led=20 (PWM_PERIOD/10) dioda będzie świecić bardzo słabo. Program18.zip Program11.zip
  14. Cortex-M3 to ciekawa platforma. Natomiast linux daje możliwości, których goły RTOS nie oferuje. Wszystko oczywiście można zrobić - linux to też program jak każdy inny. Chodzi raczej o nakład pracy. Wczoraj się zmobilizowałem i uruchomiłem kamerkę. Przed końcem konkursu artykuł powinien powstać, więc sens wykorzystania linuxa w robocie będzie lepiej widać.
  15. Ostatnio z braku czasu testy musiałem odłożyć. Jak chodzi o pierwsze pytanie, to u mnie jest jakieś 54MB wolnej pamięci RAM. Do tego 900MB Flash. Więc raczej sporo. Dostęp do PWM jest przez rejestry, nie znalazłem gotowego modułu w linux-ie. Oczywiście taki moduł można samemu napisać. Natomiast co do wykorzystania modułu do budowy robota, to na pewno nie jest to temat łatwy i dobry dla początkujących. Natomiast, czy jest to za duża armata, można polemizować. Jest to na pewno mniejszy kaliber niż montowanie PC w robocie. Pobór prądu jest całkiem znośny, silniki biorą i tak dużo więcej prądu. A jeśli jesteśmy minimalistami, to po co w ogóle używać mikrokontrolerów? Większość opisywanych na forum robotów spokojnie poradziłaby sobie bez mikrokontrolera, nawet mostek H nie jest w pełni wykorzystywany. Moim zdaniem chodzi o poznanie czegoś nowego, zdobycie doświadczenia. Nie tylko budowanie konstrukcji minimalistycznych.
  16. Aktywne pętle to nie najlepszy pomysł. Po pierwsze jeśli chcemy, aby były dokładne musimy wyłączyć przerwania. Inaczej błędy będą dowolnie duże - w zależności ile razy wywołane zostanie przerwanie. Kolejny problem to pobór prądu. Procesor bierze dużo prądu, gdy czeka w pętli. Dla ARM najlepszym rozwiązaniem jest RTOS i co prawda mniejsza dokładność odmierzania czasu, ale za to wykorzystanie mocy procesora. Jeśli potrzebujemy bardzo krótkie, dokładne pętle (czasy pojedynczych µs), to można zrobić jak na AVR. Najlepiej jest napisać to w asemblerze, inaczej kompilator (dokładniej - optymalizator) popsuje nam wyliczenia.
  17. Co do pkt.3 to moduł KAmodBTM222 ma już zainstalowany stabilizator na 3.3V. Więc nie trzeba nic dodawać, wystarczy odpowiednio ustawić zworkę.
  18. W tekście usunąłem informację o dużej dokładności. Błąd może wynosić 100µs, ale przy dłuższych opóźnieniach (rzędu setek ms) nie ma to znaczenia. Nie chciałem modyfikować licznika timera ponieważ w dalszych częściach ten sam timer będzie wykorzystywany do kilku funkcji. Nadal wolny jest Timer1, więc jeśli potrzebujemy bardzo dokładnie mierzyć czasy rzędu mikrosekund można go wykorzystać.
  19. Poprzednia lekcja Lekcja 6 - Zegary i przerwania (ciąg dalszy) Następna lekcja W poprzedniej lekcji uruchomiliśmy PLL oraz Timer0. Jeśli było to nieco niezrozumiałe, nie ma czym się przejmować. Pętli PLL nie będziemy więcej ruszać. Wystarczy wywołać procedurę uruchamiającą i można o PLL (na razie) zapomnieć. Podobnie będzie z Timer0. Zmienimy tylko częstotliwość wywoływania przerwań i więcej nie będziemy się zagłębiać w działanie timer-a. Obecna konfiguracja to wywoływanie przerwania co 1s. Jest to zdecydowanie za rzadko. Ustalmy jakiś inny okres - przykładowo 100us (czyli 10.000 razy na sekundę). W tym celu zmieniamy linijkę: T0_MR0 = 1500; Od tej pory nie będziemy wracali do procedury timer0_init(). Nasze przerwanie jest wołane 10000 na sekundę, więc do sterowania diodami raczej się nie nadaje. Wykorzystamy je jako licznik czasu. Nowa procedura będzie wyglądała następująco: volatile unsigned int t0_counter = 0; void timer0(void) __attribute__ ((interrupt ("IRQ"))); void timer0(void) { if (T0_IR&0x01) { if (t0_counter) t0_counter--; } T0_IR = 0xff;//0x01; VICVectAddr = 0; } Zmienna t0_counter przechowuje nasz licznik. Jest on co 100us zmniejszany o 1. Gdy osiągnie 0 jest zatrzymywany. Do czego nam się to może przydać? Wreszcie możemy napisać znane z AVR procedury delay_us() oraz delay_ms(). Niestety nie będziemy mogli odmierzać czasu z dokładnością do pojedynczych mikrosekund. void delay_100us(unsigned int dly) { t0_counter = dly; while (t0_counter); } void delay_ms(unsigned int ms) { t0_counter = ms*10; while (t0_counter); } Procedury działają praktycznie tak samo - ustawiają naszą zmienną licznikową, po czym czekają, aż zostanie wyzerowana. Bardzo ważne jest zadeklarowanie zmiennej t0_counter jako volatile. Inaczej optymalizator może wyrzucić nasze pętle while z kodu. Teraz możemy napisać bardzo prosty program migający diodami co pół sekundy: int main(void) { GPIO1_IODIR |= LED_MASK; pll_init(); timer0_init(); while (1) { led_swap(); delay_ms(500); } } Pełny kod znajdziemy w pliku program10.zip Program10.zip
  20. O ile dobrze widzę Ron=0,5Ohm, czyli przy 1A będzie spadek 0,5V.
  21. Poczytajcie dokładniej o SPI oraz przetwornikach C/A - to co piszecie nie zadziała. Można natomiast wykorzystać rozwiązanie analogowe. Przy nadajniku dać przetwornik napięcie->częstotliwość, za odbiornikiem częstotliwość->napięcie. Jeśli dobierzemy częstotliwości w zakresie możliwości modułów HM-T/R, powinno działać. Jak chodzi o przetworniki C/A i A/C to jedynym wyjściem jest zastosowanie mikrokontrolerów po obu stronach. To że oba moduły używają SPI nie oznacza, że po połączeniu nastąpi transmisja czegokolwiek sensownego.
  22. Nie wszystkie obsługują. Najszybsze jakie testowałem to CC1100 - prędkość do 500kbps. Natomiast tanie/proste moduły pracuję tylko z niskimi prędkościami. Nie jest to duży problem, bo takie moduły nie służą do przesyłania plików (np. filmów), do tego lepiej wykorzystać wifi. Mają za to mały pobór prądu i niską cenę. Jeśli przesyłamy niewiele danych (np. odczytujemy stan czujników, albo wysyłamy komendę dla robota), to nawet 1200bps wystarczy. [ Dodano: 24 Lip 10 09:19 ] Według noty producenta moduły HM-T obsługują do 4800bps (9600 max).
  23. Ja bym radził wczytać się w dokumentację procesora - User's Manual (http://ics.nxp.com/support/documents/microcontrollers/pdf/user.manual.lpc2109.lpc2114.lpc2119.lpc2124.lpc2129.lpc2194.lpc2210.lpc2212.lpc2214.lpc2220.lpc2290.lpc2292.lpc2294.pdf). Co prawda tylko 390 stron (nowsze procesory mają po 600), ale jest tam wszystko. Na początek może nie jest to łatwe do czytania, ale można się przyzwyczaić.
×
×
  • Utwórz nowe...