Skocz do zawartości

Elvis

Użytkownicy
  • Zawartość

    2596
  • Rejestracja

  • Ostatnio

  • Wygrane dni

    189

Wszystko napisane przez Elvis

  1. Polecenia do BTM muszą się kończyć znakiem końca linii (CR), w C wystarczy wysłać \r. Bascoma nie znam, ale może jest coś jak Println, czy printline? [ Dodano: 27 Sie 10 01:57 ] może zadziała: Print "ATN?" ; Chr(13) ;
  2. Wniosek taki, że masz źle ustawioną częstotliwość pracy procesora. Działa 8x wolniej niż zakładasz. Czyli nie jak masz w kodzie 8Mhz, ale 1Mhz $crystal = 8000000 Spróbuj na początek zmienić kod na: $crystal = 1000000 A najlepiej ustaw odpowiednio fuse-bity.
  3. Moim zdaniem FT232 nie trzeba przeprogramowywać - najczęściej domyślne ustawienia pracują w pełni poprawnie. Spróbuj użyć innego programu do obsługi COM-a. U mnie pod Termit-em też nic nie działa, a putty chodzi bez problemu. http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html
  4. Przejściówka powinna działać. Nie używałem tego konkretnie modelu, ale mam ten sam chip, więc na 99% wszystko będzie ok.
  5. Szczerze mówiąc to sam nie wiem co się stało. Robot stał spokojnie w pokoju, włączniki od płyty AVR oraz ARM9 były wyłączone. Nagle pojawił się dym. Możliwe że było spięcie przed włącznikami płytek. Ja bardziej podejrzewam, że luty płytki AVR przebiły obudowę akumulatora. W sumie nic się nie stało, ale przestrzegam przed używaniem li-pol - trzeba być baaaardzo ostrożnym.
  6. Jak chodzi o składową Y w modelu YUV to zdjęcia czarno-białe na dole artykułu powstały przez przypisanie do wszystkich składowych RGB wartości Y. Dla linefollowera myślę że wystarczyłoby to w zupełności. Jak chodzi o światłoluba to pewnie też, chociaż można byłoby dodać filtr na U-V, żeby wyodrębnić tylko interesujący nas kolor. Przedstawiłem algorytm przeliczania z YUV do RGB z kilku powodów. Po pierwsze chciałem sprawdzić, czy dobrze odczytuję dane z kamerki. Po drugie poprzednia wersja miała algorytm JPEG->RGB->analiza, chciałem porównać prędkość działania z przetwarzaniem YUV->RGB->analiza oraz samym przetwarzaniem obrazu na podstawie składowej Y. Jak widać ta kamerka pozwala na dużo szybsze działanie, chociaż szkoda że na razie nie mam na czym przetestować w pełni autonomicznej wersji robota. [ Dodano: 22 Sie 10 03:34 ] Temperatura była na tyle duża, że miedziane przewody popaliło. Serwa uległy stopieniu, w obudowach porobiły się dziury. Nawet sharp uległ stopieniu - tego mi najbardziej szkoda. Ogólnie cały robocik trafił do śmieci... Teraz zastanawiam się czym go zastąpić.
  7. Logitech-a nie polecam. Mam chyba właśnie C200 - na obudowie nie ma symbolu, ale kupowałem najtańszą. Główna wada to przesyłanie tylko i wyłącznie w formacie JPEG. Dekodowanie tego zajmuje wieki. Zaraz dopiszę do artykułu rezultat testu innej kamerki - jest dużo lepiej.
  8. Na razie największym problemem jest dekodowanie jpeg-ów. Powoduje bardzo duże opóźnienia. Dzisiaj kupiłem inną kamerkę. Na pudełku ma napisane, że obsługuje RGB24. Co ciekawe wcale nie obsługuje... Zamiast tego przesyła YUYV. Rozpoznawanie jasności powinno być więc łatwiejsze niż w RGB. A co najważniejsze obraz nie jest skompresowany. Tylko te ilości danych do przetworzenia... Bufor obrazu dla 640x480 to 600kB. Obraz pobieram w 320x240, ale to nadal 150kB na ramkę.
  9. Próbowałem zmusić kamerkę do generowania strumienia we wszystkich możliwych formatach - RGB, YUV, GRAY itd. Niestety zawsze wysyłany jest JPEG. Wydaje mi się, że to efekt wykorzystania pierwszej lepszej (najtańszej) kamerki jaką mieli w sklepie. Na przyszłość trzeba wybrać kamerkę, która: 1) obsługuje nieskompresowane dane 2) są do niej moduły v4l Co do wykrywania to liczę jasność 3 obszarów - po lewej, środek i po prawej. Następnie robot stara się skierować w tę stronę, gdzie jest najjaśniej. W teorii i na testowych obrazach wszystko działało pięknie, natomiast sam robot zachowywał się bardzo kiepsko. Zamiast jechać do światła wolał np. telewizor w sąsiednim pokoju. Lepiej byłoby wykrywać coś bardziej charakterystycznego - np. plamkę lasera. Niestety nie mam takiego, ale pewnie dałoby dużo lepsze efekty. Najpierw filtr na kolor czerwony, później wyszukiwanie. Ogólnie temat jest dość trudny, najlepiej byłoby przekompilować OpenCV na ARM i zacząć zabawę z algorytmem.
  10. Może po prostu użyj 74ac240 zamiast szukać odpowiednika? Układ można kupić za 61gr tutaj: http://sklep.slawmir.com.pl/product_info.php?products_id=17078
  11. 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.
  12. 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
  13. Wszystko jest opisane w artykule. W załącznikach jest przykładowy program, a w linkach odnośnik do stron z tutorialami.
  14. 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.
  15. Chyba będzie miał 70us, ale nie jestem pewien. Najlepiej oscyloskop podłączyć i sprawdzić.
  16. 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ć.
  17. 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.
  18. 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;} }
  19. 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.
  20. 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
  21. 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
  22. 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.
  23. 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
×
×
  • Utwórz nowe...