Skocz do zawartości

Elvis

Użytkownicy
  • Zawartość

    2603
  • Rejestracja

  • Ostatnio

  • Wygrane dni

    191

Wszystko napisane przez Elvis

  1. 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;} }
  2. 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.
  3. 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
  4. 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
  5. 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.
  6. 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
  7. 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ć.
  8. 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.
  9. 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.
  10. Co do pkt.3 to moduł KAmodBTM222 ma już zainstalowany stabilizator na 3.3V. Więc nie trzeba nic dodawać, wystarczy odpowiednio ustawić zworkę.
  11. 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ć.
  12. 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
  13. O ile dobrze widzę Ron=0,5Ohm, czyli przy 1A będzie spadek 0,5V.
  14. 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.
  15. 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).
  16. 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ć.
  17. Spokojnie, nie ma obawy, da się mieć kilka kompilatorów. Ja mam C++Builder6, VisualStudio, AVR Studio i jeszcze kilka na raz. Problem polega na tym, że nie wszystkie mogą być domyślnymi. Większość (jak np. C++ Builder) nie potrzebują ścieżki do plików - IDE poradzi sobie bez nich. Instalator dodaje je, żeby można było kompilować z linii poleceń. Ważne pytanie, jakich windowsów używasz? [ Dodano: 23 Lip 10 07:01 ] Spróbuj z wiersza poleceń uruchomić: arm-elf-gcc --version Jak nie zadziała to jeszcze: c:\winarm\bin\arm-elf-gcc --version
  18. Chyba brakuje ścieżki do kompilatora w zmiennej PATH: C:\WinARM\utils\bin;C:\WinARM\bin
  19. Prawdopodobnie miałeś "make" zainstalowany na komputerze przed instalacją WinARM. Odinstaluje ten program, albo zmień kolejność wyszukiwania w ścieżce dostępu.
  20. Bascom-a nie znam, ale program wygląda poprawnie. Musisz tylko zmniejszyć prędkość transmisji. Zamiast: $baud = 9600 Spróbuj np. 1200 $baud = 1200
  21. Poprzednia lekcja Lekcja 5 - Zegary i przerwania Następna lekcja Poznaliśmy już podstawy sterowania liniami I/O, czas zapoznać się z zegarami oraz obsługą przerwań. Procesory LPC211x mają bardzo rozbudowany układ zegarowy oraz układ przerwań. W kursie poznamy jedynie fragment, najpotrzebniejszy w praktyce. W przypadku procesorów AVR działanie było bardzo proste - zewnętrzny kwarc, albo wbudowany układ RC generował częstotliwość z którą pracował układ. Procesory ARM mają znacznie bardziej skomplikowane działanie zegara. Zewnętrzny rezonator ma częstotliwość 12MHz (w przypadku naszej płytki). Częstotliwość ta jest następnie zwiększana w pętli PLL. Dopiero taki sygnał jest podawany na procesor (zegar główny CCLK). Układy peryferyjne (np. porty I/O, timery itd) są taktowane za pomocą innego sygnału, oznaczanego PCLK. Jest to częstotliwość CCLK podzielona przez pewną wartość - po resecie wynosi ona 4. Na razie nie używamy pętli PLL, więc procesor pracuje z następującymi ustawieniami: • Fosc = 12MHz • CCLK = 12MHz • PCLK = 3MHz Znając częstotliwość taktowania wykorzystamy Timer0 do cyklicznego wywoływania procedury. Ustawimy zegar tak, aby co 1s wywoływał procedurę obsługi przerwania. W niej będziemy naprzemiennie zapalać diody D0 oraz D1. Najpierw napiszemy procedurę obsługi przerwania: void timer0(void) __attribute__ ((interrupt ("IRQ"))); void timer0(void) { if (T0_IR&0x01) { led_swap(); } T0_IR = 0xff; VICVectAddr = 0; } Instrukcja if sprawdza, przyczynę wywołania przerwania. Ostatnie dwie instrukcje zerują flagi przerwań. Procesory ARM7 mają kilka rodzajów przerwań. Warte uwagi jest jeszcze przerwanie "szybkie" FIQ - jest tylko jedno, za to jego obsługa następuje o kilka taktów zegara szybciej. Nam, aż tak bardzo na czasie nie zależy, więc wystarczą nam przerwania IRQ. Układ timera można skonfigurować na bardzo wiele sposobów. Wykorzystamy bardzo prosty: ustalimy licznik główny na czas odpowiadający 1s, po osiągnięciu przez licznik tej wartości będzie wywoływana nasza procedura przerwania. Zegar konfigurujemy następująco: T0_TCR = 2; T0_PR = 0; T0_MR0 = 12000000/4; T0_MCR = 3; T0_TCR = 1; T0_TCR - rejestr kontrolny timera. 2 resetuje zegar, 1 uruchamia T0_PR - ustala presklaler. Zero oznacza brak, czyli timer działa z częstotliwością PCLK. T0_MR0 - jest to wartość do której liczy timer. Wpisujemy równą częstotliwości PCLK, czyli przerwanie wystąpi po dokładnie 1s. T0_MCR - rejestr ustala działanie timera. 3 oznacza, że po osiągnięciu wartości wpisanej do T0_MR0 wywołane zostanie przerwanie, a timer zacznie liczyć od początku Więcej informacji o rejestrach i ich wartościach znajdziemy w dokumentacji procesora. Cała procedura inicjalizująca timer wygląda następująco: void timer0_init(void) { VICIntSelect &= ~0x10; VICIntEnable = 0x10; VICVectCntl0 = 0x24; VICVectAddr0 = (unsigned long)timer0; T0_TCR = 2; T0_PR = 0; T0_MR0 = 12000000/4; T0_MCR = 3; T0_TCR = 1; asm volatile ( "STMDB SP!, {R0} \n" "MRS R0, CPSR \n" "BIC R0, R0, #0xC0 \n" "MSR CPSR, R0 \n" "LDMIA SP!, {R0}" ); } Początek konfiguruje wektor przerwań. Układy LPC z rodziny ARM7 mają bardzo skomplikowane przerwania. Na szczęście w nowszych układach (Cortex) zostało to już uproszczone. Tutaj pozostaje przeczytać dokumentację procesora, albo wykorzystać kod na razie nie wnikając jak działa. Fragment w asemblerze uruchamia przerwania, jest to odpowiednik sei() w AVR. Większość komercyjnych środowisk ma do tego gotowe procedury, niestety darmowy gcc wymaga sięgnięcia do asemblera. W pliku program08.zip znajdziemy pełny program. Efekt działania to dwie migające na przemian diody. Uruchamiamy pętlę PLL Praca z częstotliwością 12MHz nie jest niczym nadzwyczajnym, zwykła ATMega bywa szybsza. Czas więc nieco przyspieszyć. Ustawimy PLL, tak aby mnożył częstotliwość rezonatora przez 5. Otrzymamy wtedy parametry pracy: • Fosc = 12MHz • CCLK = 60MHz • PCLK = 15MHz Procedura konfiguracji pętli PLL: void pll_init() { MAM_MAMTIM = 3; MAM_MAMCR = 2; SCB_PLLCFG = 0x24; SCB_PLLCON = 0x1; SCB_PLLFEED = 0xAA; SCB_PLLFEED = 0x55; while (!(SCB_PLLSTAT&0x0400)); SCB_PLLCON = 0x3; SCB_PLLFEED = 0xAA; SCB_PLLFEED = 0x55; } Pierwsze dwie instrukcje konfigurują pamięć Flash procesora - jest ona wolniejsza niż rdzeń, więc musimy ustawić podzielnik jej zegara na 3. Dalszy ciąg procedury konfiguruje i uruchamia pętlę PLL. Najważniejsza jest linia: SCB_PLLCFG = 0x24; Wartość 0x24 ustala mnożnik M=5, P=3. Jeśli chcemy uzyskać inną częstotliwość pracy możemy te wartości zmienić. Informacja o sposobie obliczania jest dostępna w dokumentacji procesora. Warto zapamiętać, że częstotliwość pracy wybiera program. Bywa to bardzo wygodne, ponieważ im wyższa prędkość pracy, tym wyższy pobór prądu. Procesory ARM mogą w trakcie działania zmieniać swoją prędkość działania. Pozostaje zmienić ustawienia naszego timera - zgodnie z nową częstotliwością taktowania: T0_MR0 = 60000000/4; Pełny program znajdziemy w archiwum program09.zip. Działa jak poprzedni, jednak procesor pracuje już z pełną prędkością. Autor kursu: Elvis Pomoc przy edycji materiałów wideo, formatowaniu i publikacji: Treker Program09.zip Program08.zip
  22. adam30010 jeśli się na czymś nie znasz, to lepiej się nie odzywaj. Sterowanie silnika krokowego przez stabilizator napięcia to nie jest dobry pomysł, jak napisał Sabre silniki krokowe steruje się prądowo. Tzw. chopper jest dobrą metodą aby ograniczyć prąd. Można kupić gotowe układy sterujące np. mostek L298 + sterownik z chopperem L297. Duży wybór oferuje również allegromicro http://www.allegromicro.com/en/
  23. Poprzednia lekcja Lekcja 4 - Porty I/O (ciąg dalszy) Następna lekcja Sterowanie diodami - usprawnianie programu Poprzednia wersja programu działała całkiem ładnie, jednak w pętli głównej 8 razy (tyle ile jest diod) powtarzał się ten sam kod. Nie jest to dobra metoda programowania. Po pierwsze program jest długi (wyobraźmy sobie sterowanie 100 diodami). Po drugie, niewygodnie jest program modyfikować - zmiana na zapalanie diod parami wymaga przeglądania całej pętli głównej. Jeśli wielokrotnie wykonujemy taki sam (lub prawie taki sam) program, zamiast kopiowania kodu, możemy zastosować pętlę. W naszym przypadku pętla będzie miała 8 kroków (każdy, aby zapalić kolejną diodę). Pozostaje pytanie, jak zapisać, kiedy ma się zapalić która dioda. Moglibyśmy wykorzystać operacje na bitach oraz fakt, że diody są przyłączone do kolejnych linii. Jednak w ten sposób bardzo ciężko będzie zmienić program. Użyjemy więc tablicy. Będziemy w niej przechowywać maski zapalanych diod w kolejnych krokach pokazu. const unsigned int LEDS_PATTERN[8] = { LED_0, LED_1, LED_2, LED_3, LED_4, LED_5, LED_6, LED_7 }; Definiujemy tablicę 8 liczb. Są to maski kolejno zapalanych LED-ów. Do programu dodamy licznik leds_index. Będzie on wskazywał, który indeks w tablicy mamy aktualnie "wyświetlać". int leds_index = 0; while (1) { leds_set(LEDS_PATTERN[leds_index]); leds_index++; if (leds_index>=8) leds_index = 0; delay(DELAY); } Instrukcja leds_set(LEDS_PATTERN[leds_index]); odczytuje maskę z tablicy LEDS_PATTERN i za pomocą procedury leds_set() zapala odpowiednie diody. Następnie leds_index jest zwiększane o jeden, aby przejść do kolejnego etapu wyświetlania. Za pomocą IF-a sprawdzamy, czy nie wyszliśmy poza zakres tablicy. Jeśli tak, rozpoczynamy animację od nowa. W pliku program03.zip znajdziemy pełny kod programu. Zmieniając dane w tablicy LEDS_PATTERN[] możemy zmienić układ zapalanych diod. Możemy jednocześnie zapalać kilka LED-ów, używając znanego operatora |. Poniżej widzimy działanie programu dla tablicy LEDS_PATTERN[]. Cała reszta programu pozostaje bez zmian. const unsigned int LEDS_PATTERN[8] = { 0, LED_3|LED_4, LED_2|LED_5, LED_1|LED_6, LED_0|LED_7, LED_1|LED_6, LED_2|LED_5, LED_3|LED_4 }; W pliku program04.zip znajdziemy jeszcze jedną wersję programu. Tym razem diody są zapalane najpierw w kolejności zdefiniowanej w tablicy LEDS_PATTERN[], a później od końca. Jest to przykład na wykorzystanie dodatkowej zmiennej do przechowywania kierunku zmian. W podobny sposób sterujemy kierunkiem pracy silnika krokowego. Wejścia - obsługa przycisku Gdy mamy już dość zapalania i gaszenia diodek, czas coś odczytać z linii procesora. Odnajdujemy na schemacie oraz płytce przycisk S2 - INT1. Jest on podłączony (przez zworkę INT1) do linii P0.14 procesora. Programowa obsługa wejścia jest bardzo prosta. Po resecie, linie procesora ustawiane są jako wejścia, więc nie musimy ich konfigurować (za pomocą rejestru IO0DIR). Pozostaje jedynie odczytać stan wejścia. Służą do tego rejestry: IO0PIN dla portu 0 oraz IO1PIN dla portu 1. Jeśli linia połączona jest z masą, odczytamy na odpowiednim miejscu bit o wartości 0. Jeśli ma potencjał bliski napięciu zasilania (3.3V), to odczytamy 1. Aby z 32 bitów składających się na rejestr wybrać interesujący używamy operatora koniunkcji (&). Czyli wyrażenie IO0PIN & _BV(14) daje wartość 0, gdy na linii P0.14 jest napięcie 0V (masa), oraz 1 gdy podłączymy napięcie 3.3V. Na naszej płytce rezystor R5 łączy zasilanie (3.3V) z linią P0.14. Więc, gdy S2 jest rozłączony, na linii P0.14 pojawi się logiczne 1. Po naciśnięciu S2, linia P0.14 zostanie zwarta do masy. Otrzymamy więc wartość 0. Czas napisać program, który odczyta stan przycisku S2. Pełny kod znajdziemy w pliku program05.zip while (1) { if (GPIO0_IOPIN & _BV(14)) leds_set(LED_0); if ((GPIO0_IOPIN & _BV(14))==0) leds_set(LED_4); } W języku C warunek jest prawdziwy, gdy jest dowolną liczbą różną od 0. Stąd pętla nieskończona while (1) ; ma jako warunek 1, czyli zawsze logiczną prawdę. Równie dobrze można napisać while (-5), czy while (1020). Instrukcja warunkowa "if" warunek przyjmuje w nawiasach (). Jest on obliczany jako liczba, i jeśli wynosi 0 instrukcja po if jest pomijana, jeśli cokolwiek różnego od 0 to wykonywana. if (GPIO0_IOPIN & _BV(14)) - linia P0.14 będzie podłączona do 3.3V to instrukcja po "if" zostanie wykonana (czyli leds_set(LED_0)). Natomiast if ((GPIO0_IOPIN & _BV(14))==0) działa odwrotnie - wykona instrukcję po "if" jeśli P0.14 połączymy z masą. Program działa następująco: 1) Gdy S2 jest rozwarty, na P0.14 mamy napięcie 3.3V, pierwszy if jest spełniony, wykonywana jest instrukcja po nim - zapalana jest dioda D0 2) Gdy S2 zostanie przyciśnięty, na P0.14 pojawi się potencjał masy (0V), drugi if będzie spełniony - zapali się dioda D4. Czekanie na naciśnięcie przycisku Sposób obsługi przycisków pokazany w poprzednim przykładzie jest prosty ma jednak dużą wadę. Kod zapalający diodę wykonywany jest wiele razy. Często nie chcemy, aby program tak działał - chcemy wykryć naciśnięcie przycisku, wykonać działanie dokładnie raz (np. uruchomić robota). Do czekania na zdarzenie posłuży nam pętla while(). Wykonywana jest ona tak długo, jak jest parametr jest prawdziwy (różny od zero). while (GPIO0_IOPIN & _BV(14)) ; Taka pętla będzie się wykonywać (i nic nie robić ; oznacza pustą instrukcję) tak długo, jak przycisk S2 będzie zwolniony (nie naciśnięty). Warunek działa jak poprzednio. S2 jest zwolniony, na P0.14 jest napięcie 3.3V, odczytujemy wartość bitu 1, więc warunek jest prawdziwy. Aby poczekać na zwolnienie przycisku, zmieniamy warunek pętli. while ((GPIO0_IOPIN & _BV(14))==0) ; Pętla główna wygląda następująco: while (1) { while (GPIO0_IOPIN & _BV(14)) ; leds_index++; if (leds_index>=8) leds_index = 0; leds_set(LEDS_PATTERN[leds_index]); while ((GPIO0_IOPIN & _BV(14))==0) ; } Najpierw czekamy na naciśnięcie S2. Zapalamy kolejną diodę (kod wygląda znajomo - był opisywany wcześniej). Następnie czekamy na zwolnienie S2. Drgania styków Osoby, które wcześniej programowały obsługę mikroprzełączników zwrócą uwagę, że program ma błąd - przy naciskaniu i zwalnianiu przycisku generowanych jest wiele bardzo krótkich impulsów. Są one efektem drgania styków wewnątrz przełącznika. Powinniśmy je eliminować za pomocą kilkukrotnego odczytu stanu przycisku. Jednak na naszej płytce ewaluacyjnej program działa w pełni poprawnie. Dzieje się tak za sprawą kondensatora C9. Eliminuje on drgania styków sprzętowo. Jest to układ wart zapamiętania - czasem zamiast komplikować program, można po prostu dodać kondensator. Pełny kod źródłowy przykładu znajdziemy w pliku program06.zip. W pliku program07.zip znajdziemy nieco inny program - działa podobnie, jednak zamiast zapalać kolejne diody, przy kolejnych naciskaniach S2 zapala lub gasi diodę D0. Schemat działania jest identyczny jak omawianego przykładu. Program07.zip Program06.zip Program05.zip Program04.zip Program03.zip
×
×
  • Utwórz nowe...