Skocz do zawartości

Elvis

Użytkownicy
  • Zawartość

    2594
  • Rejestracja

  • Ostatnio

  • Wygrane dni

    189

Wszystko napisane przez Elvis

  1. Nie wszystkie wyświetlacze wymagają napięcia 5V. Niektóre mają wbudowaną przetwornicę. Zobacz np. na stronie farnella. Ceny są znaczne, ale jest trochę wyświetlaczy na 3V. [ Dodano: 14-12-2011, 21:50 ] Przykładowy wyświetlacz na 3V: http://pl.farnell.com/fordata/fdcc1602l-flyybw-91le/disp-lcd-16x2-3v-g-bl/dp/1847940
  2. Ja bym radził odczytać cały scratchpad z DS1820 i sprawdził CRC. Z tymi czujnikami wcale nie tak łatwo się dogadać. Możliwe że błąd jest w samej transmisji po 1-wire. Możesz też spróbować zmienić temp0, temp1 z char na coś większego. Możliwe, że kompilator źle rozumie: temp=(float)(temp1+(temp2*256))/16;
  3. Na AVR można zmienić bez problemu. Po stronie PC odbieraj normalnie, jako 8 bitowe dane. Można też spróbować innego terminala po stronie PC. Polecam putty: http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html A swoją drogą - jeśli komunikacja PC<->moduł i AVR<->moduł działa poprawnie to trzeba tylko wysłać ramkę konfigurującą prędkość 40000 z obu urządzeń.
  4. Moim zdaniem problem jest w niedokładności zegarów. Bit stop-u jest przez odbiornik interpretowany jako najwyższy bit i stąd problem. Proponuję taki eksperyment: zmień format danych na 9 bitów danych, 1 bit stopu i prześlij wartość 0x43.
  5. Ja też widziałem na własne oczy jak takie błędy wyglądają. Na labkach tego nie przerabiałem, za to w pracy wielokrotnie. Oczywiście, że enkodery są wykorzystywane, ale nie są takie proste w użyciu jak mogą się wydawać. Zapewniam, że błędy będą dużo większe niż +- jedna podziałka. Ale to trzeba "na własnej skórze" doświadczyć, żeby uwierzyć. Jako jedno ze źródeł problemów polecam sprawdzić co dzieje się podczas drgania - jeśli tarcza enkodera zatrzyma się na granicy między poziomami odczytu. Jeśli do tego dodamy problem zmiany kierunku ruchu, to robi się bardzo wesoło. Główna wada jest taka, że jak już raz "zgubimy" krok enkodera, to nie tylko o tym nie wiem, ale kolejne gubienia mogą się dodawać do naszego błędu. Stąd po pewnym czasie działania błędy mogą być znaczne.
  6. W przypadku enkodera inkrementalnego błędy będą się kumulować. Konieczne więc byłoby dodanie układu zerowania położenie i powrót to takiego miejsca co pewien czas. Potencjometr nie ma takiej wady, działa jak enkoder absolutny. A co do dokładności to głównie zależy od jakości potencjometru oraz jakości przetwarzania sygnału. Jeśli po każdym pomiarze układ wraca do poziomu "zerowego" i jesteś w stanie łatwo dodać układ zerowania, to enkoder sprawdzi się bardzo dobrze. Natomiast jeśli układ ma się np. wychylić, a później przez pewien czas wykonywać ruchy bez powrotu do zera, to polecam potencjometr. Największą wadą potencjometru jest jego zużywanie się. Z czasem jego parametry będą ulegały pogorszeniu.
  7. Poprzednia część kursu. [Kurs] Programowanie ARM LPC1114 cz.7 - Czujniki odbiciowe W poprzednich częściach kursu poznaliśmy już możliwości procesora niezbędne do zbudowania prostego robota. Potrafimy sterować silnikami, odczytywać poziom napięcia za pomocą przetwornika analogowo-cyfrowego. Tym razem wykorzystamy poznaną wiedzę do wykrywania linii, które jest podstawą do budowy naszego linefollowera. W pierwszej części kursu pojawił się schemat płytki z czujnikami odbiciowymi. Zastosowałem czujniki TCRT5000. Warto w kilku słowach omówić ich działanie. Każdy czujnik składa się z 2 elementów: 1) diody oświetlającej podłoże 2) fototranzystora wykrywającego światło odbite od podłoża Fotodioda i fototranzystor pracuje w podczerwieni, jeśli chcemy sprawdzić, czy dioda działa prawidłowo można wykorzystać aparat bez filtru. Większość telefonów wyposażonych jest właśnie w takie aparaty. Niestety czujniki użyte w moim robocie zostały wymontowane z poprzednich konstrukcji. Jak widać działają nie najlepiej. Zdjęcie od razu ujawnia, które czujniki na pewno nie będą działały zadowalająco. Dioda zasilana jest przez rezystor i układ nie wymaga chyba omówienia. Warto za to chwilę popatrzeć na schemat podłączenia fototranzystora. Przez fototranzystor płynie prąd zależny jego oświetlenia. Nas interesuje napięcie w punkcie OPTO1 (jest ono podawane na wejście przetwornika A/C). Napięcie OPTO1 wynosi VCC minus spadek na rezystorze R2. Czyli Uopto1 = Vcc - U2. Z prawa Ohma spadek napięcia na rezystorze R2 wynosi: U2 = R2 * i, gdzie i to prąd płynący przez rezystor. Jednocześnie prąd i jest to prąd płynący przez fototranzystor naszego czujnika. Gdy fototranzystor nie jest oświetlany, płynie przez niego bardzo mały prąd, więc i spadek napięcia na rezystorze R2 jest mały. Oznacza to, że poziom napięcia w punkcie OPTO1 jest prawie taki jak VCC. Gdy oświetlimy fototranzystor, zacznie przez niego płynąć większy prąd. Spowoduje to zwiększenie spadku napięcia na R2, a w rezultacie zmniejszenie napięcia na OPTO1. Czyli zależność jest taka: • fototranzystor oświetlony - niskie napięcie na OPTO1 • fototranzystor nieoświetlony - wysokie napięcie na OPTO1 Jak będzie oświetlany nasz fototranzystor? Przez światło diody odbite od podłoża. Więc zależność będzie następująca: • jasne podłoże - niskie napięcie na OPTO1 • ciemne podłoże (linia) - wysokie napięcie na OPTO1 Jak pamiętamy z poprzedniej części, napięcie na przetworniku A/C zamieniane jest na liczbę z zakresu 0-1023. Im wyższe napięcie tym wyższa wartość odczytana przez czujnik. Na podstawie tego co ustaliliśmy oznacza to, że niskie wartości - odpowiadają jasnemu podłożu. Wysokie wartości z przetwornika - ciemne podłoże / linię. Czas sprawdzić, czy wszystko działa prawidłowo. Podłączamy fototranzystory do wejść procesora. Ja wybrałem 4 działające czujniki i 4 przetworniki. Niestety podczas wcześniejszych prac uszkodziłem procesor, więc nie wszystkie przetworniki działają prawidłowo. Na szczęście dostępne jest 8 wejść, udało się wśród nich znaleźć 4 sprawne. Wgrywamy program18.zip do procesora (jest to właściwie poprzedni przykład, który odczytuje tylko wybrane kanały ADC). Poniżej widzimy rezultat dla robota nad białą kartką: Porównajmy wyniki z czarną kartką: I jeszcze biała kartka z czarną linią pod czujnikiem AD4: (warto zwrócić uwagę, że ta kartka jest nieco „bielsza” niż użyta poprzenio). Wyniki są zgodne z naszymi oczekiwaniami - im ciemniejsze tło, tym wyższe odczyty. Właściwie w tej chwili moglibyśmy już napisać program sterowania robotem. Jednak postaramy się wykonać jeszcze jedną czynność. Jak widać moje czujniki są bardzo kiepskiej jakości - szczególnie AD1 daje inne odczyty niż pozostałe. Co więcej odczyty będą zależały od koloru podłoża / warunków oświetlenia. Aby pozbyć się wszystkich tych niedoskonałości, przygotujemy program do kalibracji czujników. Kalibracja czujników Kalibracja będzie przebiegać następująco: Dla każdego czujnika będziemy zapisywać najmniejszą i największą wartość odczytaną przez czujnik. W tym czasie czujniki powinny zostać przesunięte nad podłożem i linią. Najmniejsza wartość będzie odpowiadała barwie podłoża, najwyższa - linii. Programowo będziemy skalować odczyty do wspólnego przedziału 0-100. Czyli odczyt po przeskalowaniu 0 będzie oznaczał czyste tło, 100 czarną linię. Pośrednie odczyty pozwolą na wykrywanie częściowego przesłonięcia linii. Omawiany przykład znajdziemy w pliku program19.zip. Wykorzystywane będą 4 czujniki, będą im odpowiadały indeksy od 1 do 4. Dodatkowo dodane zostaną 2 skrajne, udawane czujniki tzw. atrapy. Będą one zawsze zawierały odczyt 0 - czyli podłoże. Dodanie ich ułatwi napisanie algorytmu sterowania robotem. Na początek deklarujemy 4 tablice: int32_t opto_min[6]; int32_t opto_max[6]; int32_t opto_value[6]; int32_t opto_scaled[6]; W tablicach opto_min[] oraz opto_max[] będziemy zapisywać najniższe i najwyższe odczyty danego czujnika. Tablica opto_value[] będzie przechowywać ostatni odczyt z przetwornika A/C. Natomiast tablica opto_scaled[] będzie zawierała odczyty z czujników po przeliczeniu na wartości 0-100. Zdefiniujemy też kilka funkcji pomocniczych. Funkcja opto_read() odczytuje wartości z przetworników (i zapisuje w opto_value[]). Następnie wywołujemy opto_scale(), która przelicza odczytane ostatnio wartości i zapisuje w tablicy opto_scaled[]. Zanim będziemy mogli używać funkcji opto_scale() musimy wykonać kalibrację. Do kalibracji przygotujemy funkcję calib_update() - zaktualizuje ona wpisy w tablicach opto_min[] oraz opto_max[]. Pozostaje jeszcze pytanie, jak przesuwać czujniki podczas kalibracji. Można to oczywiście wykonać ręcznie, ale nie o to chodzi podczas budowy robota. Wykonamy więc obrót w lewą i prawą stronę, tak aby pod czujnikami znalazła się linia trasy. Niestety brak mostka H nie daje możliwości obrotu w miejscu. Mimo wszystko kalibrację można wykonać. Funkcja opto_calib() wykona za nas kalibrację. Najpierw obróci robota w prawo, następnie w lewo i znowu w prawo, aby wrócić do pierwotnej pozycji. Po kalibracji przeskalowane odczyty wyglądają następująco: Jak widać linia znajduje się pod czujnikiem 3 i częściowo 2. Dzięki wykorzystaniu przetworników A/C dostajemy nie tylko robota, który dopasowuje się do warunków (autokalibracja), ale jeszcze możemy wykrywać częściowe najechanie czujnika na linię. program18.zip program19.zip
  8. Zobacz układ L297. Zawiera sterownik do L298 i umożliwia sterowanie prądowe, czyli właśnie zadanie jaki prąd maksymalnie może przez silniki płynąć.
  9. Poprzednia część kursu. [Kurs] Programowanie ARM LPC1114 cz.6 - ADC W tym odcinku zajmiemy się działaniem przetwornika analogowo-cyfrowego (ang. Analog-to-Digital Converter). Mikrokontroler LPC1114 jest wyposażony w jeden, 10-bitowy przetwornik. Wejście przetwornika podłączone jest przez multiplekser do 8 wejść procesora. Możemy wybrać, sygnał z którego wejścia chcemy odczytać. • AD0 - P0.11 • AD1 - P1.0 • AD2 - P1.1 • AD3 - P1.2 • AD4 - P1.3 • AD5 - P1.4 • AD6 - P1.10 • AD7 - P1.11 Na płytce ZL32ARM znajdziemy potencjometr P1 podłączony do pinu P0.11, czyli kanału AD0 przetwornika A/C. Obracając potencjometrem możemy zmieniać napięcie podawane na wejście przetwornika. Przetwornik zmienia napięcie na liczby. Jak wiemy przetwornik jest 10-bitowy. Oznacza to, że pozwala na odczyt 2^10, czyli 1024 poziomów napięcia. Są one reprezentowane jako liczby od 0, do 1023. Jeśli na wejście przetwornika podamy napięcie 0V, odczytamy wartość 0, gdy napięcie 3.3V, odczytamy 1023. Ogólnie przeliczenie z napięcia, na wartość wyraża wzór: ADC = 1023 * Vad / 3.3 Podobnie, mając odczytaną wartość z przetwornika, możemy obliczyć napięcie na przetworniku: Vad = 3.3 * ADC / 1023 Gdy znamy podstawy działania przetwornika, czas napisać program odczytujący wartość z przetwornika. W pliku program16.zip znajdziemy omawiany program. Opis zaczniemy niejako od końca. Mamy dwie funkcje: adc_init() adc_get(int channel) Pierwszą należy wywołać raz na początku programu. Druga funkcja odczytuje wartość z przetwornika o podanym numerze. Wykorzystamy wcześniej podane układy peryferyjne, aby w zależności od wartości z przetwornika: • regulować częstotliwość migania diody LED • wysyłać odczytaną wartość z przetwornika przez RS-232 • sterować prędkością silnika zależnie od położenia potencjometru Pętla główna programu wygląda następująco: while(1) { val = adc_get(0); uart_send_int(val); uart_send("\n"); if (val>=0) { pwm_set(val/10, 100-val/10); GPIOClear(LED_1); delay(val/50); GPIOSet(LED_1); delay(20-val/50); } } Funkcja uart_send_int wysyła wartość liczbową (odczytaną z przetwornika) przez RS-232. Funkcja pwm_set służy do sterowania prędkością silników robota. Opóźnienia (funkcja delay) regulują częstotliwość migania diody. Program jak widać jest bardzo krótki, Poniżej rezultat działania: Teraz zajmijmy się funkcjami obsługującymi konwerter. • adc_init() Ta funkcja ma dwa zadania. Po pierwsze musi uruchomić przetwornik (domyślnie wyłączony, aby oszczędzać prąd). Realizują to następujące instrukcje: LPC_SYSCON->PDRUNCFG &= ~0x0010; LPC_SYSCON->SYSAHBCLKCTRL |= 0x2000; Kolejnym krokiem jest ustawienie funkcji pinów przetwornika. Na razie wykorzystamy tylko kanał AD0: LPC_IOCON->R_PIO0_11 = 0x02; • adc_get Przed konwersją ustawiamy parametry pracy przetwornika. Realizuje to linia: LPC_ADC->CR = ADCR_CLKDIV|(1<<channel); channel to numer kanału, z którego będziemy odczytywać dane (u nas 0). Natomiast stała ADCR_CLKDIV określa dzielnik zegara dla przetwornika A/C. Procesor jak pamiętamy działa z częstotliwością 48MHz. Natomiast przetwornik wymaga zegara o częstotliwości co najwyżej 4.5MHz. Musimy więc podzielić częstotliwość zegara systemowego przez 11 (48MHz/11 = 4.36MHz). Możemy podać większą wartość dzielnika, natomiast należy unikać przekraczania częstotliwości 4.5MHz - może to spowodować błędne działanie przetwornika. Stała ADCR_CLKDIV zawiera wartość dzielnika pomniejszoną o 1. Chcemy używać dzielnik 11, więc definiujemy: #define ADCR_CLKDIV (10<<8) Dalej następuje uruchomienie konwertera: LPC_ADC->CR |= 0x01000000; Oczekiwanie na zakończenie konwersji: while (((val=LPC_ADC->GDR) & 0x80000000)==0); Na koniec wyłączenie konwertera i zapisanie wyniku: LPC_ADC->CR &= ~0x01000000; if ((val & 0x40000000)==0) { result = (val>>6) & 0x3ff; } Jeśli jest to nieco zawiłe, nie należy się poddawać, w przykładowych programach znajdziemy gotową funkcję obsługi konwertera. Więcej kanałów przetwornika Umiemy już odczytywać dane z kanału 0 (AD0), czas odczytać wszystkie 8 kanałów. Kolejny przykład znajdziemy w pliku program17.zip. Funkcje dotyczące przetwornika A/C zostały przeniesione do nowego modułu (pliki adc.h oraz adc.c). Tym razem wszystkie piny podłączone do konwertera są ustawiane jako wejścia przetwornika analogowo-cyfrowego: LPC_IOCON->R_PIO0_11 = 0x02; // P0.11 <- AD0 LPC_IOCON->R_PIO1_0 = 0x02; // P1.0 <- AD1 LPC_IOCON->R_PIO1_1 = 0x02; // P1.1 <- AD2 LPC_IOCON->R_PIO1_2 = 0x02; // P1.2 <- AD3 LPC_IOCON->SWDIO_PIO1_3 = 0x02; // P1.3 <- AD4 LPC_IOCON->PIO1_4 = 0x01; // P1.4 <- AD5 LPC_IOCON->PIO1_10 = 0x01; // P1.10 <- AD6 LPC_IOCON->PIO1_11 = 0x01; // P1.11 <- AD7 Ponieważ przesyłamy znacznie więcej danych przez RS-232, zwiększona została prędkość transmisji. Zamiast jak dotychczas 9600, dane wysyłane są z prędkością 115200. Poniżej widzimy rezultat działania programu: Odczyt z kanału AD0 możemy regulować potencjometrem, pozostałe wymagają podłączenia źródła sygnału. Ponieważ docelowym przykładem będzie robot, pozostałe wejścia zostaną podłączone do optoelementów. Napięcie na przetworniku będzie się zmieniało w zależności od koloru podłoża. program16.zip program17.zip
  10. zmień pętlę while na: while (1) { PORTB |= (1<<PB2); _delay_ms(2000); PORTB &= ~(1<<PB2); _delay_ms(2000); }
  11. Poprzednia część kursu. [Kurs] Programowanie ARM LPC1114 cz.5 - RS-232 W poprzedniej wersji kursu komunikacja przez RS-232 została opisana dla procesora LPC-2114. Nowy procesor działa bardzo podobnie i w artykule opiszę jedynie różnice między układami. Zachęcam, więc najpierw do przeczytania poprzedniego kursu: • tutaj znajdziemy opis komunikacji • biblioteka standardowa (printf) • bootloader Nowy procesor posiada jeden moduł UART, zamiast jak LPC2114 dwa. Pozwala to na obsługę jednego połączenia RS-232. Część o komunikacji poprzez RS232 prawie bez zmian odnosi się do nowego procesora. Biblioteka standardowa powoduje ogromne wydłużenie kodu, więc nie polecam jej stosowania. Niestety darmowy kompilator, jakim jest gcc nie najlepiej sobie z tym radzi. Odcinek o bootloaderze jest w pełni aktualny na nowym procesorze. Tutaj też znajdziemy bootloader, jego działanie jest identyczne jak poprzednio. Na początek inicjalizujemy moduł UART odpowiedzialny za komunikację przez RS-232. #define BAUDRATE 9600 #define BAUDRATEDIVISOR (48000000/(BAUDRATE*16)) void uart_init(void) { LPC_SYSCON->SYSAHBCLKCTRL |= 0x1000; LPC_IOCON->PIO1_6 = 0x01; LPC_IOCON->PIO1_7 = 0x01; LPC_UART->FCR = 0x07; LPC_UART->LCR = 0x80; // DLAB = 1; LPC_UART->DLL = BAUDRATEDIVISOR & 0x00ff; LPC_UART->DLM = (BAUDRATEDIVISOR >> 8) & 0x00ff; LPC_UART->LCR &= ~0x80; // DLAB = 0; //Set mode LPC_UART->LCR = 0x03; //8 bit word length, 1 stop bit, No parity } Jak widać kod jest właściwie identyczny jak poprzednio. W definicji stałej BAUDRATEDIVISOR zmieniona została częstotliwość zegara, tym razem wynosi ona 48MHz, zamiast 15MHz jak poprzednio. Poza tym dostęp do rejestrów odbywa się przez znane już nam wskaźniki. Inaczej ustawiamy też funkcję wyjść I/O. Zamiast rejestru PINSEL pojawia się kod: LPC_IOCON->PIO1_6 = 0x01; LPC_IOCON->PIO1_7 = 0x01; Nie powinno być zaskoczeniem, że funkcja wysyłająca dane jest również podobna do poprzedniej wersji: void uart_send_byte(const uint8_t byte) { while(!(LPC_UART->LSR & 0x20)); LPC_UART->THR = byte; } W pliku program13.zip znajdziemy przykładowy program. Program kompilujemy i wgrywamy do procesora jak zwykle. Niestety gdy go uruchomimy, pojawiają się problemy. Przyczyną jest znany nam z poprzedniej części bootloader. Linie RTS i DTR interfejsu RS-232 sterują resetem procesora oraz pinem uruchamiającym bootloader. W efekcie nasz program nie działa, gdy uruchamiamy terminal komunikacyjny. Musimy zmienić program do komunikacji na taki, który pozwoli nam na sterowanie liniami RTS oraz DTR. Polecam program Hercules, dostępny na stronie: http://www.hw-group.com/products/hercules/index_en.html Uruchamiamy program, wybieramy zakładkę „Serial”, a następnie w ramce po prawej stronie ustawiamy parametry komunikacji: • Name - nazwa portu do którego podłączony jest moduł • Baud - prędkość transmisji 9600 • Data size - 8 • Parity - none • Handshake - OFF • Mode - Free Następnie naciskamy przycisk „Open” i jeśli zostaną zaznaczone opcje „DTR” lub „RTS” to je wyłączamy: Teraz jak widać mamy działającą komunikację przez RS-232, a jednocześnie możemy programować układ. Wystarczy nacisnąć „Close” i za pomocą FlashMagic-a można wgrywać program. Postępowanie jest więc następujące: 1) zaprogramuje procesor programem FlashMagic 2) przełącz się na Hercules, naciśnij Open 3) gdy wykonasz testy, naciśnij Close 4) zmień kod programu, skompiluj nową wersję 5) przełącz się na FlashMagic i wróć do pkt.1 Mając więc 3 uruchomione programy (LPCExpresso, FlashMagic, Hercules) możemy zmieniać, wgrywać i testować komunikację przez RS-232. Kolejnym krokiem jest wydzielenie funkcji związanych z komunikacją do nowego modułu. W pliku program14.zip znajdziemy gotowy program. Czas wykorzystać zdobytą wiedzę do sterowania naszym robotem. Kod programu znajdziemy w pliku program15.zip. Sterowanie silnikiem zostało opisane w odcinku o PWM. Odbieranie danych realizuje funkcja: uint8_t uart_recv_byte(void) { while(!(LPC_UART->LSR & 0x01)); return LPC_UART->RBR; } Pętla główna programu wygląda następująco: while(1) { c = uart_recv_byte(); switch (c|32) { case 'w': pwm_set(100, 100); break; case 's': pwm_set(25, 25); break; case 'a': pwm_set(50, 0); break; case 'd': pwm_set(0, 50); break; default: pwm_set(0, 0); } uart_send_byte(c); } Jak widać jest to sterowanie robotem za pomocą znanej kombinacji znaków WSAD. Sztuczka w instrukcji switch (c|32) pozwala na ignorowanie wielkości znaków. Można wysyłać małe lub duże litery, aby sterować robotem. Mamy więc zdalnie sterowanego robota. Oczywiście jest to sterowanie przez RS-232, czyli kablowe. Możemy łatwo to zmienić stosując gotowy moduł radiowy, np. bluetooth. program13.zip program14.zip program15.zip
  12. Zobacz jeszcze raz, czy wszystko podłączyłeś jak w dokumentacji układu. Tak powinno wyglądać podłączenie: Datasheet jest tutaj: http://focus.ti.com/lit/ds/symlink/max232.pdf
  13. Jak chodzi o bibliotekę ST to ja za nią nie przepadam. Jest nieoptymalna, ale to jak każda biblioteka, nie można narzekać. Moim zdaniem jest brzydka - wypełnianie niekończących się struktur jest paskudne, ale to rzecz gustu. Natomiast tym co mnie do biblioteki zniechęciło to jej zmiany. Sam już to zobaczyłeś - masz przykład, a nie działa bo nowa wersja bibliteki... Ja interesowałem się STM32 już jakiś czas temu. Byłem niesamowicie zaskoczony, jak po może roku przerwy stare programy nie chciały się skompilować... ST przygotowało nową bibliotekę i musiałem się wszystkiego uczyć od nowa... Teraz mam uraz.
  14. No właśnie... Dlatego myślę, że warto poświęcić trochę czasu na poznanie TS - podobnie jak LPCExpresso bazuje na eclipse + gcc. Oczywiście komercyjne środowiska mają swoje zalety, ale cena jest nieco zaporowa...
  15. Przez 30 dni można używać "evaluation edition" - czyli pełna funkcjonalność. Po tym czasie zostaje tylko "Kickstart edition", z ograniczeniem do 32KB kodu. Poza tym jest jeszcze problem ew. "komercyjnego" wykorzystywania kompilatora. TS można pisać programy komercyjne, pod IAR najpierw musimy kupić licencję. Swoją drogą - ktoś wie ile taka licencja kosztuje?
  16. 30 dni, a później 32KB kodu... albo spooooory wydatek. Jak chodzi o TS to całkiem dobre środowisko, a nie bardzo rozumiem problem z SysTick. Wystarczy użyć SysTick_Config(), działa bez problemu.
  17. Jak chodzi o IAR to warto sprawdzić ile ten kompilator kosztuje...
  18. Źle jest wpięty kondensator 1uF na samej górze. Powinien być między pin 2, a GND. Na datasheecie jest oznaczony jako C3.
  19. Poprzednia część kursu. [Kurs] Programowanie ARM LPC1114 cz.4 - PWM Programowy PWM Podobnie jak w poprzednim kursie przygotujemy programowe sterowanie jasnością diod. Mamy do dyspozycji tylko 2 diody, ale to wystarczy do nauki. W pliku program11.zip znajdziemy kod programu. W poprzednim kursi znajdziemy sporo informacji o PWM: https://www.forbot.pl/forum/topics20/kurs-programowania-arm-cz7-pwm-vt3926.htm Wykorzystamy timer jak w poprzednim przykładzie, ale zmienimy częstotliwość wywoływania procedury przerwania. Tym razem będziemy wywoływać ją co 100us. Wystarczy zmienić wartość rejestrów MR0 oraz PR. W procedurze obsługi przerwania wywołamy procedurę pomocniczą pwm_proc(). Działa ona tak jak opisywana dokładnie w poprzedniej edycji kursu - realizuje programowy PWM na liniach podłączonych do diod LED1 i LED2. Działanie PWM zostało już opisane w poprzednim kursie. Warto zwrócić na nową funkcję delay() - pozwala ona na odczekiwanie przez zadany czas (parametrem jest opóźnienie w jednostkach 10ms). Nieco mylące jest sterowanie diodami - wystawiając logiczne 0 zapalamy diody, a 1 gasimy. Więc wypełnienie PWM 0% oznacza maksymalną jasność diody, a 100% - jej wygaszenie. Stąd w wyliczeniach niejako odwrotne sterowanie. Sprzętowy PWM Umiemy już programowo obsługiwać PWM, czas poznać możliwości sprzętowego PWM. Sprzętowe rozwiązanie jest pod wieloma względami znacznie lepsze niż programowe. Nie obciąża procesora, jest znacznie dokładniejsza niż realizacja programowa. Następny przykład znajdziemy w pliku program12.zip. Celem programu jest sterowanie silniczkami robota. Za pomocą joysticka na płytce ZL32ARM będziemy sterować robotem. Wykorzystamy znany nam już 16 bitowy timer 0, tym razem uruchomimy w nim tryb PWM. Do wyprowadzeń P0_9 oraz P0_10 podłączamy sterownik silników. W opisywanym przykładzie są to po prostu tranzystory BC337. Równie dobrze, a nawet lepiej można byłoby podłączyć mostek H, przykładowo dobrze znany L293. W poprzedniej części kursu wspomniane zostały funkcje alternatywne linii procesora. Teraz z nich skorzystamy. Domyślnie P0_9 oraz P0_10 są to linie wejścia-wyjścia. My chcemy, żeby działał na nich sprzętowy PWM. Zmieniamy działanie linii następującym kodem: LPC_IOCON->PIO0_9 = 0x02; LPC_IOCON->SWCLK_PIO0_10 = 0x03; Dokładny opis kodów dla każdego pinu znajduje się w dokumentacji procesora. Po wykonaniu powyższego kodu linie P0_9 i P0_10 zamiast zwykłymi I/O stają się wyjściami modułu PWM. Musimy dokonać drobnej zmiany w naszej konfiguracji timera. Po pierwsze uruchamiamy PWM na tym timerze pisząc: LPC_TMR16B0->PWMC = 0x06; Kod uruchamia wyjścia MAT1 i MAT2 timera (są one wyprowadzone na liniach P0_9 i P0_10). Kolejna zamiana to przypisanie rejestrom MR1 i MR2 wartości wypełnienia linii PWM: LPC_TMR16B0->MR1 = PWM_PERIOD; LPC_TMR16B0->MR2 = PWM_PERIOD; Czeka nas tutaj pewna niespodzianka. PWM układu LPC1114 działa w ten sposób, że na początku okresu PWM na wyjściu wystawiane jest logiczne 0, po doliczeniu do wartości rejestru MR1 (albo MR2), wyjście zmienia wartość na 1. Oznacza to, że wpisanie do MR1 wartości 0 daje 100% wypełnienie PWM, a wartość MR1 - daje na wyjściu 0%. Czyli niejako odwrotnie niż byśmy tego oczekiwali. Aby ułatwić sterowanie silnikami przygotujemy funkcję, która przyjmie (w procentach) wypełnienie PWM, dokona odpowiednich przeliczeń i ustawi odpowiednie rejestru. Kod tej funkcji wygląda następująco: void pwm_set(int32_t l_proc, int32_t r_proc) { LPC_TMR16B0->MR1 = PWM_PERIOD*(100-l_proc)/100; LPC_TMR16B0->MR2 = PWM_PERIOD*(100-r_proc)/100; } Prosty program główny pozwoli nam na sterowanie robotem: if (GPIOGet(JOY_U)==0) pwm_set(100, 100); else if (GPIOGet(JOY_OK)==0) pwm_set(25, 25); else if (GPIOGet(JOY_L)==0) pwm_set(50, 0); else if (GPIOGet(JOY_R)==0) pwm_set(0, 50); else pwm_set(0, 0); Sterowanie silnikiem - znaczenie diod zabezpieczających Postanowiłem zaryzykować uszkodzenie tranzystorów sterujących i przepadać wpływ diod na pracę układu. Na początek podłączyłem silnik bez diod zabezpieczający. Pierwszy program nie wykorzystywał PWM, jedynie w pełni załączał silnik. Co ciekawe diody nie okazały się niezbędne. Jak widać na oscylogramie poniżej, wszystko działało poprawnie. Jednak uruchomienie PWM diametralnie zmieniło sytuację. Poniżej widać oscylogramy (po prawej zmieniona jest skala - indukowane napięcie było tak duże, że nie było widać go przy poprzedniej skali) Co prezentują powyższe oscylogramy? Przy rozłączaniu zasilania silników indukuje się całkiem znaczne napięcie. Jak widać jest to ponad 25V! Dopóki stale zasilaliśmy silnik nic złego się nie działo. Jednak, gdy używamy PWM, każde rozłączanie zasilania indukuje niekorzystne dla nas napięcie. Mój układ wytrzymał takie sterowanie, jednak potencjalnie możemy w ten sposób uszkodzić sterownik silnika, a nawet procesor. Sytuacja jest jeszcze gorsza, gdy zablokujemy silnik. Po lewej stronie widać oscylogram przy pracy silników bez obciążenia, po prawej gdy zablokujemy koła: Teraz pojawia się napięcie ponad 45V! Na początek dodamy diodę równolegle do tranzystora - taka dioda jest wbudowana w większość tranzystorów MOSFET. Jak widać dioda ta niewiele pomaga. Musimy więc dodać kolejne diody - tym razem równolegle z uzwojeniami silnika. Teraz wreszcie sytuacja jest nieco lepsza. Napięcie nieznacznie przekracza 7V. Ja zastosowałem tanie i wolne diody 1N4148. Gdyby zastosować diody szybkie (schotky'ego) uzyskalibyśmy jeszcze lepsze rezultaty. Wniosek jest następujący: diody zabezpieczające są niezbędne. Warto wspomnieć o jeszcze jednym efekcie - z diodami silniki uzyskały znacznie większy moment i prędkość przy takim samym wysterowaniu. program11.zip program12.zip
  20. Poprzednia część kursu. [Kurs] Programowanie ARM LPC1114 cz.3 - zegary i przerwania Na początek dobra wiadomość. Rdzenie Cortex mają znacznie poprawioną obsługę przerwań w porównaniu do starszej rodziny ARM7TDMI. Inżynierowie firmy ARM wzięli sobie do serca krytykę poprzednich procesorów i dodali do rdzenia znacznie poprawioną obsługę przerwań. Przed lekturą tego artykułu zachęcam do przeczytania piątej części poprzedniego kursu, w której poruszona została pokrewna tematyka. Najpierw zajmiemy się przerwaniami od układów licznikowych, tzw. timerów. Procesor LPC1114 jest wyposażony w 5 timerów. Dwa są 16-bitowe, dwa 32-bitowe oraz jeden specjalny, który ma rozdzielczość 24-bitów. Rejestry 16 i 32 bitowe są to liczniki tzw. ogólnego zastosowania. Oferują bogatą funkcjonalność, w szczególności mogą działać jako PWM. Ostatni timer (SysTick timer) ma specjalne zastosowanie - służy do zapewniania podstawy czasu. Jest to bardzo wygodne rozwiązanie dla każdego projektanta systemu. Timer systemowy Najpierw musimy napisać procedurę obsługi przerwania. Jest to nadzwyczaj łatwe zadanie. Wystarczy napisać funkcję o nazwie SysTick_Handler. Co powinna robić taka funkcja? Na początek może po prostu zwiększać globalny licznik czasu. Mamy więc kod: volatile uint32_t global_timer = 0; void SysTick_Handler(void) { global_timer++; } Chcemy, aby zmienna global_timer była zwiększana co powiedzmy, 10 ms. Konfiguracja jest bardzo łatwa dzięki funkcji SysTick_Config zdefiniowanej w pliku core_cm0.h (plik został dołączony wraz z biblioteką CMSIS). Aby uruchomić przerwanie piszemy: SysTick_Config(480000); // 10ms Wartość 480 000 wynika z prędkości działania procesora - jest on skonfigurowany do pracy z częstotliwością 48MHz. Więc 48 000 000 / 480 000 daje nam 100Hz, czyli oczekiwane 10ms. W pliku program09.zip znajdziemy przykład wykorzystania timera do domierzania czasu zapalania i gaszenia diod. Dla dociekliwych Ktoś może zapytać, jak to się dzieje, że procesor działa z prędkością 48MHz. Odpowiedź znajdziemy w pliku system_LPC11xx.c znajdującym się w bibliotece CMSIS. Znajdziemy tam funkcję SystemInit. Uruchamia ona pętlę PLL, domyślnie wykorzystuje zewnętrzny kwarc 12MHz i konfiguruje procesor do pracy z podaną prędkością. Zmieniając plik system_LPC11xx.c możemy łatwo zmienić konfigurację systemu. Kolejne pytanie to nazwa funkcji - skąd bierze się akurat SysTick_Handler. Aby się o tym przekonać możemy otworzyć plik cr_startup_lpc11.c - został on automatycznie dodany do naszego projektu. W pliku tym znajdziemy domyślne funkcje obsługujące przerwania. W szczególności funkcję obsługi interesującego nas przerwania. Jest ona oznaczona słówkiem WEAK, dzięki czemu nasza funkcja niejako zastąpi tę domyślną. Warto jeszcze przyjrzeć się funkcji ResetISR. Jest ona wywoływana po resecie (więc i przy uruchomieniu) mikrokontrolera. Ta funkcja najpierw wywołuje SystemInit, a następnie nasz program - funkcję main(). W pliku cr_startup_lpc11.c znajdziemy również domyślne funkcje obsługi pozostałych przerwań. Możemy je przedefiniować, aby zapewnić własne funkcje obsługi przerwań. Timery ogólnego przeznaczenia Mamy już globalny licznik czasu, teraz poznamy 16-bitowy licznik czasu. W pliku program10.zip znajduje się opisywany przykład. Program uruchamia 2 timery. Timer systemowy oraz 16 bitowy timer 0. Ten timer musimy skonfigurować sami. W tym celu wykorzystujemy wskaźnik LPC_TMR16B0. Pełny opis rejestrów znajdziemy w dokumentacji, następujący kod uruchamia timer: LPC_SYSCON->SYSAHBCLKCTRL |= 0x80; // CT16B0 clock enable LPC_TMR16B0->TCR = 2; // timer reset LPC_TMR16B0->MCR = 3; // reset and interrupt on MR0 LPC_TMR16B0->MR0 = 48000; // 1 ms LPC_TMR16B0->PR = 9; // prescaler 10 LPC_TMR16B0->TCR = 1; // timer enable NVIC_EnableIRQ(TIMER_16_0_IRQn); // interrupt enable Pierwsza linijka jest konieczna, aby moduł timera zaczął działać. Aby oszczędzać prąd procesor początkowo ma uruchomione tylko niezbędne moduły. Za pomocą rejestru MCR ustawiamy tryb działania timera - chcemy aby liczył do wartości MR0, następnie wywoływał przerwanie i liczył od początku. Do MR0 wprowadzamy wartość 48000, więc przerwanie byłoby wywoływane co 1ms. Wykorzystujemy preskaler w rejestrze PR aby zamiast co 1ms wywoływać przerwanie co 10ms. Do rejestru PR wpisujemy wartość o 1 mniejszą niż oczekiwane „spowolnienie” timera. Funkcja NVIC_EnableIRQ uruchamia nasze przerwania. Pozostaje jeszcze dodać funkcję obsługi przerwania. Podobnie jak poprzednio wystarczy napisać funkcję o nazwie TIMER16_0_IRQHandler. void TIMER16_0_IRQHandler(void) { static uint8_t bit = 0; if (bit) { GPIOClear(LPC_GPIO2, 0x80); bit = 0; } else { GPIOSet(LPC_GPIO2, 0x80); bit = 1; } LPC_TMR16B0->IR = 0x1f; // clear interrupt flag } Funkcja na pinie P2_7 generuje sygnał prostokątny. Jeśli mamy pod ręką oscyloskop, możemy zobaczyć jak działa nasze przerwanie. Ostatnia linijka funkcji jest bardzo ważna. W niej zerujemy flagę przerwania. Inaczej zaraz po zakończeniu funkcji zostałaby ona wywołana ponownie. Kolejna część naszego kursu, w której omówiony zostanie PWM pojawi się już za tydzień. program09.zip program10.zip
  21. Zobacz: Poprzednia część kursu! Programowanie ARM LPC1114 cz.2 - porty I/O Porty wejścia-wyjścia są niewątpliwie najważniejszymi układami peryferyjnymi każdego procesora. W poprzedniej wersji kursu opisywałem dokładnie ich działanie. Teraz skoncentruję się nad zmianami w nowym mikrokontrolerze. Podobnie jak poprzednio, najważniejsza jest umiejętność korzystania z dokumentacji procesora. Całą dokumentację znajdziemy na stronie producenta. Najciekawszy jest dokument nazwany „User Manual” (co ciekawe Datasheet to jedynie ogólny opis procesora). Link do dokumentacji: http://ics.nxp.com/support/documents/microcontrollers/pdf/user.manual.lpc11xx.lpc11cxx.pdf Podobnie jak poprzednio, w dokumentacji możemy znaleźć rozmieszczenie pinów procesora: W procesorze LPC2114 porty I/O były 32-bitowe. Nowy procesor ma również 32-bitowe porty, jednak każdy port ma maksymalnie 12 pinów. Ułatwia to adresowani linii procesora. Dostępne są 4 porty: PORT0 PORT1 PORT2 PORT3 Piny oznakowywane są jako: PIO0_0, PIO0_1, itd. PIOx to numer portu, a część po podkreśleniu (_) to numer linii. Sterowanie wyjściami procesora Dostęp do portów nieco się zmienił w nowej wersji procesora. Zamiast bezpośrednio odwoływać się do rejestrów, wykorzystujemy wskaźniki. Każdy port posiada zdefiniowany wskaźnik na strukturę w pamięci. Szczegóły znajdziemy w pliku LPC11xx.h. Do portów odwołujemy się więc przez wskaźniki: LPC_GPIO0 LPC_GPIO1 LPC_GPIO2 LPC_GPIO3 Każdy wskaźnik wskazuje na strukturę nazwaną LPC_GPIO_TypeDef. Jej pola odpowiadają rejestrom procesora. Przykładowo pole DIR odpowiada za kierunek działania linii wejścia-wyjścia. Ustawienie bitu ustawia linię jako wyjście (identycznie jak w przypadku rejestrów DDRA, DDRB itd. procesorów AVR). W zestawie ZL32ARM dioda LED1 podłączona jest do linii P0_6. Aby ustawić tę linię jako wyjście użyjemy kodu: LPC_GPIO0->DIR |= _BV(6); Nie należy się więc obawiać wskaźników. Jest to nieco inny zapis niż byliśmy dotychczas przyzwyczajeni, ale działanie jest identyczne jak dotychczas. Kolejny rejestr który będzie nas interesował to rejestr DATA. Zapis do niego ustawia stan linii danego portu. Działa więc identycznie jak odwołanie do PORTA, PORTB, itd. w procesorach AVR. Aby zapalić diodę LED1 wykonujemy kod (zapalamy wystawiając logiczne zero!) LPC_GPIO0->DATA &= ~_BV(6); Diodę gasimy podobnie: LPC_GPIO0->DATA |= _BV(6); Rejestr DATA można również wykorzystać do odczytania stanu linii, gdy pracuje ona jako wejście. Wykorzystanie rejestru DATA nie jest najlepszym rozwiązaniem, ale na początek może wydawać się najłatwiejsze. Pozwala na programowanie w stylu znanym z procesorów AVR. W pliku program01.zip znajdziemy przykładowy program. Ustawia on linię P0_6 jako wyjście, a następnie cyklicznie zapala i gasi diodę LED1. Plik program02.zip zawiera kolejny przykład, tym razem sterowane są diody LED1 oraz LED2 i zapalane naprzemiennie. Dostęp maskowany Korzystanie z rejestru DATA może powodować problemy, gdy w programie używane będą przerwania. Dodatkowo problemem może być jednoczesne ustawianie i wygaszanie bitów - konieczne może być wykonanie najpierw zerowanie, następnie ustawianie bitów. Aby w jednej instrukcji umożliwić zapis zarówno zer jak i jedynek do wybranych linii portu, dodany został rejestr MASKED_ACCESS. Rejestr ma postać tablicy o rozmiarze 4096 (czyli 2^12). W celu dostępu do wybranych linii zapisujemy (lub odczytujemy) pod indeks taki jak maska zmienianych bitów. Przykładowo, załóżmy że do linii P1_4, P1_5, P1_6, P1_7 mamy podłączony wyświetlacz alfanumeryczny. Chcemy w jednej instrukcji zapisać wartość 5 do linii wyświetlacza, nie zmieniając jednocześnie pozostałych wartości. Chcemy więc wyzerować P1_7, P1_5, a ustawić P1_4 i P1_6, nie zmieniając stanu pozostałych linii. Za pomocą rejestru DATA moglibyśmy wykonać następujący kod: LPC_GPIO1->DATA |= _BV(6)|_BV(4); LPC_GPIO1->DATA &= _BV(5)|_BV(6); Aby wykorzystać rejestr MASKED_ACCESS, najpierw musimy ustalić jaka jest maska naszego zapisu. Maska ma „1” tam gdzie chcemy zmieniać dane, a „0” w miejscach które mają pozostać niezmienione. Czyli powinna wyglądać następująco: Możemy ją zapisać jako: _BV(7)|_BV(6)|_BV(5)|_BV(4) Albo, co chyba nieco wygodniejsze po prostu: 0x0f0 Teraz możemy w jednej instrukcji dokonać zapisu danych: LPC_GPIO1->MASKED_ACCESS[0x0f0] = 0x050; Podobnie można odczytywać stan wybranych linii. Przykład użycia rejestru MASKED_ACCESS znajdziemy w pliku program03.zip. W kodzie zaprezentowane są 2 wersje. Pierwsza jednocześnie zapala diodę LED2 i gasi LED1: LPC_GPIO0->MASKED_ACCESS[LED_MASK] = LED_1; Druga wersja oddzielnie odwołuje się do każdego pinu. Ustawienie bitu (czyli zgaszenie diody) realizuje kod: LPC_GPIO0->MASKED_ACCESS[LED_1] = LED_1; Natomiast wygaszenie bitu: LPC_GPIO0->MASKED_ACCESS[LED_2] = 0; Funkcje dostępu do pinów Przyznam, że wykorzystanie rejestru MASKED_ACCESS chociaż wydajne, nie zawsze jest intuicyjne. Aby nieco ułatwić programowanie, zdefiniujemy funkcje do ustawiania i wygaszania odpowiednich linii procesora. Funkcja ustawiająca „1” na odpowiednich wyjściach wygląda następująco: void GPIOSet(LPC_GPIO_TypeDef* gpio, uint32_t pin) { gpio->MASKED_ACCESS[pin] = pin; } Natomiast ustawienie „0” wygląda tak: void GPIOClear(LPC_GPIO_TypeDef* gpio, uint32_t pin) { gpio->MASKED_ACCESS[pin] = 0; } Warto zwrócić uwagę na parametr gpio. Jest to wskaźnik na strukturę opisującą port. Dzięki temu funkcja może obsługiwać każdy port GPIO procesora. W pliku program04.zip znajdziemy opisane funkcje oraz przykład ich użycia: while(1) { GPIOSet(LPC_GPIO0, LED_1); GPIOClear(LPC_GPIO0, LED_2); for (i=0;i<1000000;i++) ; GPIOClear(LPC_GPIO0, LED_1); GPIOSet(LPC_GPIO0, LED_2); for (i=0;i<1000000;i++) ; } W pliku program05.zip zobaczymy pewne ułatwienie w definiowaniu, gdzie podłączone są diody. Zamiast wpisywać LPC_GPIO0 w wywołaniu GPIOClear/GPIOSet definiujemy następujące stałe: #define LED_1_PIN _BV(6) #define LED_2_PIN _BV(7) #define LED_1 LPC_GPIO0, LED_1_PIN #define LED_2 LPC_GPIO0, LED_2_PIN Dzięki takiej definicji kod jest jeszcze łatwiejszy do analizy, a zmiana podłączenia peryferiów na inny port wymaga jedynie zmiany w definicji. GPIOSet(LED_1); GPIOClear(LED_2); Plik program06.zip zawiera kolejną wersję programu, tym razem definicje podłączeń układów zostały przeniesione do nowego pliku hardware.h. Ułatwia to dostosowywanie programu do zmian w elektronice. Opisy podłączeń są zebrane w oddzielnym pliku. Odczyt stanu linii Gdy potrafimy już sterować liniami procesora, czas odczytać stan linii. W przykładowym zestawie znajdziemy joystick, którego stan postaramy się odczytywać. Jak widać przycisk środkowy (JOY_OK) jest podłączony do portu 0, pozostałe do portu 1. Dzięki opisanej wcześniej definicji linii nie stanowi to problemu. #define JOY_OK_PIN _BV(3) #define JOY_U_PIN _BV(8) #define JOY_OK LPC_GPIO0, JOY_OK_PIN #define JOY_U LPC_GPIO1, JOY_U_PIN Teraz potrzebujemy funkcji odczytu stanu linii. Wygląda następująco: int GPIOGet(LPC_GPIO_TypeDef* gpio, uint32_t pin) { if (gpio->MASKED_ACCESS[pin]) return 1; return 0; } Aby sprawdzić kod w działaniu, będziemy zapalać diodę po przyciśnięciu przycisku joysticka (trzeba pamiętać, że na wejściu pojawia się 0, gdy przycisk jest naciśnięty): if (GPIOGet(JOY_OK)==0) GPIOClear(LED_1); else GPIOSet(LED_1); W pliku program07. zip znajdziemy przykład odczytu stanu linii wejścia-wyjścia. Własna biblioteka funkcji Kolejny plik - program08.zip zawiera opisywane wcześniej funkcje wydzielone do nowego modułu. Pliki gpio.c oraz gpio.h będą wykorzystywane w kolejnych programach. Dodatkowe funkcje Warto wspomnieć o pozostałych możliwościach portów I/O. Większość pinów oprócz działania jako wejście-wyjście może też pełnić inne funkcje, jak chociażby wyprowadzenia modułów UART, I2C, czy SPI. Do konfiguracji funkcji alternatywnych służy wskaźnik LPC_IOCON. Wskazywana struktura posiada pola dopowiadające kolejnym pinom. Możemy w niej ustawić funkcje pełnione przez linie, włączyć lub wyłączyć rezystory podciągające i histerezę. Z możliwości tych będziemy korzystać w kolejnej części kursu - gdy będziemy poznawać komunikację przez RS-232 oraz działanie przetwornika analogo-cyforwego (ADC). Warto również wspomnieć o możliwości generowania przerwań w reakcji na zmianę stanu pinu (lub poziom). Nie będziemy tego wykorzystywać, jednak czasem może być to użyteczne - szczególnie, gdy zależy nam na natychmiastowej reakcji np. na wykrycie przeszkody czujnikiem. program01.zip program02.zip program03.zip program04.zip program05.zip program06.zip program07.zip program08.zip
  22. Ja chodziłem do szkoły bardzo nisko w rankingach. A nie uważam, żeby to było dużym problemem
  23. Teraz to Ty robisz wodę z mózgu... Nie bez powodu mówimy "uczyć się" - jeśli sam się nie nauczysz, nikt za Ciebie tego nie zrobi. Więc szkoła, prestiż, kadra itd. to nie wszystko
  24. To moje doświadczenie, każdy ma prawo do swojego. Ale jak widzę ludzi po technikum na analizie matematycznej, to nie jest łatwo...
  25. Jeśli mogę dołączyć się do dyskusji, to zagłosuję za liceum. Sam dawno temu chciałem iść do technikum, bo interesowałem się chemią. W międzyczasie zupełnie mi się odwidziało i zmieniłem zainteresowania. Jak pójdziesz do LO to możesz iść na właściwie dowolne studia. A kto wie co będzie Ci się podobało za kilka lat.
×
×
  • Utwórz nowe...