Skocz do zawartości
danrok

[assembler] Liczniki

Rekomendowane odpowiedzi

Witam,

tym razem mam trochę inny problem. Z przerwaniami timera.

Chce napisać program, który co 10ms będzie mi generował przerwanie.

Wszedłem więc na stronkę: http://www.et06.dk/atmega_timers/ i wpisałem

częstotliwość procesora 1 [MHz] i częstotliwość timera 10 [Hz].

Ustawiłem sobie prescaller na 8 i według tabeli wartość rejestru

TCNT1 powinna wynosić: 0x30D4. Po skompilowaniu i przerzuceniu

programu do ATmegi przerwania owszem, generują się, lecz ich

częstotliwość na oko, wynosi jakieś 2 [Hz]. Nie wiem, czy to jest

błąd jakiś? Mógłby ktoś zerknąć?

 

;************************************************************************;*					Program główny										*;************************************************************************.INCLUDE "m16def.inc";		dolaczenie biblioteki dla atmega32;deklaracja stalych w programie.EQU	K_DIODY = DDRA;		rejestr kierunku diody.EQU	O_DIODY = PORTA;	rejestr wyjscia diody.EQU 	SYS_FREQ = 1;		czestotliwosc pracy procesora.DEF ARG1_1		= R20; argument lewy.DEF ARG1_2		= R21; starszy bit.DEF ARG2_1		= R22; mlodszy bit arg prawego.DEF ARG2_2		= R23; starszy bit.DSEG;				DANA w pamięci SRAM.ORG 0x0060;		poczatek SRAMKlaw_inf: .BYTE 1;	bajtowa zmienna wczytanej liczby.CSEG.ORG	0	jmp Reset;		inicjalizacja	.ORG INT0addr;		przerwanie	jmp Odczyt_klawiatury;	odczytanie co klikniete.ORG OC0addr;	jmp DiodaReset:		;inicjalizacja	ldi R17, high(RAMEND);	ldi R16, low(RAMEND);	out SPH,R17;	out SPL,R16;	wskaznik stosu	ldi R16,0xFF;	out K_DIODY,R16;	diody w tryb wyjsciowy	rcall Ini_klawiatury;	inicjalizacja wyswietlacza i klawiatury matrycowej	rcall Ini_LCD;	clr R16;	sts Klaw_inf, R16;	sei;		odblokowanie przerwan	clr ARG1_1	clr ARG1_2	clr ARG2_1	clr ARG2_2	ldi ARG2_1, 1	ldi ARG2_2, 0Petla:				; pętla główna programu		lds	R16, Klaw_inf; odczyt zmiennej z SRAM	sbrs R16, 7rjmp Petla	lds R16, Klaw_inf	cbr R16, 1<<7	cpi R16, 1	breq Ini_Timer	rjmp PetlaIni_Timer:	push R16	ldi R17, 1<<CS11	out TCCR1B, R17	ldi R19, 0xD4	out TCNT1L, R19	ldi R19, 0x30	out TCNT1H, R19	ldi R16, 1<<TOV1	out TIFR, R17	ldi R17, 1<<TOIE1	out TIMSK, R17	pop R16rjmp Koniec	Dioda:	ldi R30, 1	ldi R31, 0	ldi R19,0b11111111	out O_DIODY, R19	ldi R16, 1	rcall Czekaj_ms	ldi R19, 0b00000000	out O_DIODY, R19	add ARG1_1, R30	adc ARG1_2, R31;	inc ARG1_1;	rcall Czysc_LCD;	rcall WypiszretiKoniec:	lds R16, Klaw_inf	sbrc R16,7	rjmp Koniecrjmp Petla.include "wait.inc".include "lcd.inc".include "klawiatura.inc".include "czekaj_us.inc".include "operacje.inc"

Udostępnij tę odpowiedź


Odnośnik do odpowiedzi
Udostępnij na innych stronach

Gość <account_deleted>

Na dzień dobry widać 2 błędy:

1. ładujesz do timera wartość 0x30D4 - ok, tyle że trzeba to zrobić w przerwaniu, najlepiej zaraz na początku, żeby utrzymać stałą częstotliwość. Ponadto powinno się tą liczbę skorygować o czas wywołania przerwania, chociaż przy sterowaniu LEDami nie jest to super ważne.

2. powyższe jednak to tylko wierzchołek problemu - podstawowy błąd to nie zapisanie zawrtości rejestru SR oraz innych używanych w przerwaniu - może to całkowicie skaszanić skoki warunkowe, wyniki obliczeń a nawet spowodować zwis programu w tle. Przerwanie w normalnych warunkach ma być niewidoczne - rejestry na stos i z powrotem panie kolego ;)

 

Całkowicie inna sprawa: czy na pewno prawidłowo ustawiłeś "fusy" dla procka?

Udostępnij tę odpowiedź


Odnośnik do odpowiedzi
Udostępnij na innych stronach

Ok, dzięki, zmieniłem troszkę program na taki:

 

;************************************************************************;*					Program główny										*;************************************************************************.INCLUDE "m16def.inc";		dolaczenie biblioteki dla atmega32;deklaracja stalych w programie.EQU	K_DIODY = DDRA;		rejestr kierunku diody.EQU	O_DIODY = PORTA;	rejestr wyjscia diody.EQU 	SYS_FREQ = 1;		czestotliwosc pracy procesora.DEF ARG1_1		= R20; argument lewy.DEF ARG1_2		= R21; starszy bit.DEF ARG2_1		= R22; mlodszy bit arg prawego.DEF ARG2_2		= R23; starszy bit.DSEG;				DANA w pamięci SRAM.ORG 0x0060;		poczatek SRAMKlaw_inf: .BYTE 1;	bajtowa zmienna wczytanej liczby.CSEG.ORG	0	jmp Reset;		inicjalizacja	.ORG INT0addr;		przerwanie	jmp Odczyt_klawiatury;	odczytanie co klikniete.ORG OC0addr;	jmp DiodaReset:;inicjalizacja	ldi R17, high(RAMEND);	ldi R16, low(RAMEND);	out SPH,R17;	out SPL,R16;	wskaznik stosu	ldi R16,0xFF;	out K_DIODY,R16;	diody w tryb wyjsciowy	rcall Ini_klawiatury;	inicjalizacja wyswietlacza i klawiatury matrycowej	rcall Ini_LCD;	rcall Ini_Timer	clr R16;	sts Klaw_inf, R16;	sei;		odblokowanie przerwan	clr ARG1_1	clr ARG1_2	clr ARG2_1	clr ARG2_2	ldi ARG2_1, 1	ldi ARG2_2, 0Petla:		; pętla główna programu		lds	R16, Klaw_inf; odczyt zmiennej z SRAM;	sbrs R16, 7rjmp Petla	lds R16, Klaw_inf	cbr R16, 1<<7	cpi R16, 1	breq Ini_Timer	rjmp PetlaIni_Timer:	push R16	ldi R17, 1<<CS11	out TCCR1B, R17	ldi R19, 0xD4	out TCNT1L, R19	ldi R19, 0x30	out TCNT1H, R19	ldi R16, 1<<TOV1	out TIFR, R17	ldi R17, 1<<TOIE1	out TIMSK, R17	pop R16ret	Dioda:	push	R16; umieszczanie na stosie zawartości rej. R16,	push	R17;   rejestru R17,	push	ZL;   rejestru R30,	push	ZH;   rejestru R31,	in	R16, SREG	push	R16	ldi R30, 1	ldi R31, 0	ldi R19,0b11111111	out O_DIODY, R19	ldi R16, 1	rcall Czekaj_ms	ldi R19, 0b00000000	out O_DIODY, R19	add ARG1_1, R30	adc ARG1_2, R31;	inc ARG1_1;	rcall Czysc_LCD;	rcall Wypisz	pop	R16; ściąganie ze stosu zawartości rej. SREG,	out	SREG, R16	pop	ZH;   rejestru R31,	pop	ZL;   rejestru R30,	pop	R17;   rejestru R17,	pop	R16;   oraz rejestru R16	retiKoniec:	lds R16, Klaw_inf	sbrc R16,7	rjmp Koniecrjmp Petla.include "wait.inc".include "lcd.inc".include "klawiatura.inc".include "czekaj_us.inc".include "operacje.inc"

Niewiele to jednak zmieniło, gdyż dalej uzyskuję 2Hz, zamiast 10.

 

EDIT:

 

FUSE jest prawidłowo ustawione. Zgodnie z książką :)

 

Dziwne, po zmianie prescalera na 1 i wpisaniu do TCN1 wartości 0x2710 przerwanie generuje się co 1/10sekundy. Gdy chcę mieć 1kHz, według strony wpisuję 0x3E8, co nie zmienia nic... Może są jakieś ograniczenia, czy coś? Sam nie wiem o co chodzi. Ma ktoś pomysł?

Edytowane przez danrok

Udostępnij tę odpowiedź


Odnośnik do odpowiedzi
Udostępnij na innych stronach

Gość <account_deleted>

Jeśli chcesz uzyskać generator określonej cząstotliwości, to trzeba przeładowywać stan licznika w przerwaniu, ponieważ inaczej ciężko przewidzieć ile czasu upłynie pomiędzy kolejnymi update'ami licznika. W tym konkretnym przypadku przerwanie powinno wyglądać jakoś tak:

...	in	R16, SREG	push	R16	ldi R19, 0x30	;ponowne ustawienie czasu do kolejnego przerwania (uwaga: atomic write! )	out TCNT1H, R19	ldi R19, 0xD4	out TCNT1L, R19...	pop	R16	out	SREG, R16...
Jeśli ustawiasz timer poza przerwaniem, to może dojść do takiej sytuacji, że zapisujesz 0x30D4 przed wystąpieniem przepełnienia - czyli timer znów liczy od tej wartości i nie generuje przerwania.

 

;-------------------------

Jednak o ile powyższe jest jak najbardziej prawdziwe, to nie jest bezpośrednią przyczyną, znalazłem jeszcze jeden błąd:

Używasz Timer1 w trybie Normal, co oznacza, że TOP=0xFFFF.

 

1/10Hz = 100ms

prescaler: 1/8*1000000=125000 T1clk/s

100ms=12500 T1clk

TCNT1(100ms) = 0x10000-12500 = 65536-12500 = 53036 -> TCNT1=0xC2FC,

 

natomiast dla 0x30D4 timer odlicza 53036 T1clk, czyli:

1/[(1/125000)*53036] = 2.35688966Hz ! ;) !

 

tip odnośnie rejestrów: ATmel ma rejestrów pod dostatkiem -> dobrze jest przeznaczyć kilka z nich tylko dla przerwań - dzięki temu nie trzeba wyrzucać ich na stos - szybszy i prostszy kod ;)

 

tip2: polecam także korzystanie z low() & high(), co znacznie ułatwia życie, f.e. :

 

ldi R19, high(53036)	;---- 	out TCNT1H, R19	ldi R19, low(53036)	out TCNT1L, R19
Edytowane przez tomazzi

Udostępnij tę odpowiedź


Odnośnik do odpowiedzi
Udostępnij na innych stronach

To też nie zdało egzaminu. Ok, napisze jak ja to liczę, może znajdziemy razem błąd:

 

 

Procesor ma częstotliwość 4MHz. Czyli 4000 000 cykli/sekunde. Wynika z tego, że 1 cykl zajmuje mu 0,25us. Chcę odmierzać czas powiedzmy co 10ms. Czyli 10ms/0,25us = 4 000 i to wstawiam do TCNT1? Aha, prescaler ustawiony na 1:1 powiedzmy, dla ułatwienia.

Udostępnij tę odpowiedź


Odnośnik do odpowiedzi
Udostępnij na innych stronach

Gość <account_deleted>

Patrz na przykład wyżej: w trybie Normal Timer liczy w górę, do wartości TOP (0xFFFF). Przerwanie jest generowane na przepełnieniu, czyli gdy TCNT1 zmienia wartość z 0xFFFF na 0x0000. Jeśli więc chcesz wygenerować przerwanie po 4000T1clk, to wpisujesz wartość 0x10000-4000 bo tyle brakuje do przepełnienia ;)

 

Sprawdź program w symulatorze - masz licznik impulsów clk, co pozwola sprawdzić poprawność koniguracji/kodu

 

Niestety nie można wprost zmierzyć aktualnej częstotliwości zegara procesora (jedynie Tiny13 ma taką możliwość) jednak jest prosta metoda pośrednia: piszesz pętle trwającą f.e 10mln imulsów i sygnalizujesz koniec np. zgaszeniem diody. Czas mierzysz stoperem i przeliczasz.

Udostępnij tę odpowiedź


Odnośnik do odpowiedzi
Udostępnij na innych stronach

Ah, fakt fakt ;) Chyba byłem jeszcze zaspany. Dzisiaj spróbuję to wykonać i dam znać jak poszło :)

 

Dzięki z góry:)

 

EDIT:

Dzięki, wstawiłem tam tę wartość co obliczyłem i faktycznie jest co 10ms. Muszę dokładniej czytać dokumentację:)

 

Dzięki jeszcze raz @tomazzi.

 

EDIT2:

 

Na następny ogień idzie TWI, albo hmm ... jeszcze coś wymyślę ;) Macie jakieś pomysły na rozwijające programy na zestawie ZL3AVR? ;) Może obsługa RS232?

Edytowane przez danrok

Udostępnij tę odpowiedź


Odnośnik do odpowiedzi
Udostępnij na innych stronach

Gość <account_deleted>

TWI/I2C masz sprzętowe - nic ciekawego imo :rolleyes:

 

ciekawsze będzie na pewno 1-Wire (softwareowe) ;)

... tu drobna uwaga: to co można znaleźć np. na elektrodzie to najgorsze z możliwych rozwiązań - cokolwiek byś nie napisał będzie lepsze...

 

btw, poza ekstremalnymi przypadkami funkcje typu wait_ms to marnowanie prądu (bo można położyć procka spać) lub mocy obliczeniowej (bo można w tym czasie zrobić coś innego) - gaszenie i zapalanie diody można zrobić całkowicie na przerwaniach.

Udostępnij tę odpowiedź


Odnośnik do odpowiedzi
Udostępnij na innych stronach

Hmm, no tak, wiem, że to w ogóle nie jest wydajne i tak się nie pisze, ale to pierwsze programy w asm są ;)

O tym spaniu procesora już czytałem co nieco. Wydaje się to być użyteczne :) Zastanawia mnie jednak jedna

rzecz.

Jak fizycznie w procesorze realizowane jest przerwanie? Wszędzie można przeczytać, że to nie obciąża procesora,

ale przecież musi on conajmniej porównywać stan licznika z przerwaniem. A co jeśli chodzi o przerwanie zewnętrzne,

musi monitorować stan wejścia na tym pinie i to pewnie też zabiera moc obliczeniową. Jak to w końcu jest?

Udostępnij tę odpowiedź


Odnośnik do odpowiedzi
Udostępnij na innych stronach

Gość <account_deleted>

Przerwanie jak najbardziej obciąża procesor - przecierz jest wykonywany dodatkowy kod a sam proces generacji zabiera trochę czasu. Istnieje też pojęcie "powodzi przerwań" - IRQ Flood, które oznacza że wykonywane są tylko przerwania np. na skutek awarii hw, błędu w programie itp.

Są różne poziomy uśpienia - różne zachowania procka, ale ogólnie patent polega na tym, że wyłączane są niektóre (większość) funkcjonalnych bloków CPU, dzięki czemu zmniejsza się pobór energii ale jednocześnie procek nadal ma możliwość śledzenia tego co się dzieje dookoła. Fizycznie odbywa się to najczęściej poprzez wyłączenie zegara dla określonych bloków CPU - co jest wystarczające, ponieważ bez zegara płyną tylko prądy upływnościowe na poziomie 10^-9A. Dokumentacja do AVR jest dość lakoniczna w tym temacie, ale jest wszysko co potrzeba do napisania softu ;)

Udostępnij tę odpowiedź


Odnośnik do odpowiedzi
Udostępnij na innych stronach

Aha, no dobra, to czytałem o tym trochę, muszę doczytać jeszcze jak to jest z obudzaniem takiego śpiocha. Mam jeszcze jedno pytanie. Mianowicie, czy programy które piszę są w miarę uniwersalne? Tzn czy przeniesienie ich na inną ATmegę, powiedzmy 16, będzie wiązało się z przepisywaniem całości kodu, czy drobnych zmian stylistycznych? Często to jest właśnie wymieniane jako minus języka Assebler?

Udostępnij tę odpowiedź


Odnośnik do odpowiedzi
Udostępnij na innych stronach

Gość <account_deleted>

... czy programy które piszę są w miarę uniwersalne? Tzn czy przeniesienie ich na inną ATmegę, powiedzmy 16, będzie wiązało się z przepisywaniem całości kodu, czy drobnych zmian stylistycznych? Często to jest właśnie wymieniane jako minus języka Assebler?

Pozwolę sobie być uszczypliwy: ludzie którzy mówią że assembler jest mało portowalny, nieczytelny czy trudny w debugowaniu to cieniasy, którzy nigdy nic nie napisali w asm, albo jeśli już, to przepisali bez zrozumienia kawałek kodu z książki :lol: Ja napisałem w asm kilka systemów opracyjych, parę modeli matematycznych różnych obiektów i inne takie - jakoś nie narzekam... wręcz przeciwnie.

 

Każdy kod pójdzie na każdym AVR, oczywićsie z uwzględnieniem różnic w dostępnym hw, liście instrukci oraz rozmiarze RAM/Flash/EEprom. Akurat ATmega16 i 32 różnią się o ile pamiętam tylko ilością pamięci - więc nie ma praktycznie żadnych problemów. Aby uczynić źródło maksymalnie uniwersalne należy w części deklaracyjnej stworzyć aliasy także dla zasobów sprzętowych - dzięki temu ew. modyfikacja trwa 10s ;) f.e. zamiast TCNT1 deklarujesz IO_Timer1 (H&L)

Każda funkcja powinna mieć lokalne aliasy rejestrów (def/undef) - dzięki czemu jest łatwo portowalna do innego projektu lub do makra. Poza tym właśnie: makrodefinicje - jest to ogromne ułatwienie - można rozszerzać listę "rozkazów" CPU (np. LDIW, PUSHW, DIVU, itp) albo stworzyć zestawik funkcji który pozwoli pisać niemal tak łatwo i szybko jak w Basicu/C.

Oprócz tego przy większych projektach przydają się funkcje operujące na predefiniowanych datatype - coś jak new() w C. Każdy programista robi to po swojemu, ale najczęściej wykorzystuje się do tego jeden ze wskaźników (X,Y,Z) + tablicę offsetów.

Przykładowy datatajp dla sensora 1-wire - jak jest ich więcej niż 2-4, to jest wręcz niezbędny do życia ;)

.dseg.overlap;thermal sensor, 1-Wire.org	0x0000	m1Wts_ID:		.byte 1;ID number	m1Wts_portPIN:	.byte 1;port pin for connection - bit mask	m1Wts_snSTATUS:	.byte 1;sensor state.equ	b1W_sstEnabled	=7	;sensor enabled, link ok.equ	b1W_sstIDpass	=6	;sensor passed power-on ID check.equ	b1W_sstPresence	=5	;presence pulse ok..equ	b1W_sstLinkGND	=4	;data wire grounded: CRC=0 & data=0.equ	b1W_sstComFault	=3	;com. fault (CRC or no response from slave).equ	b1W_sstUseSubstVal=2	;when sensor/link permament damage detected use substitute value as 'current reading'.equ	b1W_sstPout		=1	;Bus-powered operation request/active;.equ	b1W_sstDtaToRcv	=0	;receive data related to cmd - cleared if done	m1Wts_CorrOffset:	.byte 2;sensor tuning: offset	m1Wts_CorrGain:	.byte 2;sensor tuning: gain	m1Wts_SubstVal:	.byte 2;substitute value reported when link or sensor fails	m1Wts_ROMID_FC:	.byte 1;ROM ID: family code	m1Wts_ROMID_SN:	.byte	6;ROM ID: serial number	m1Wts_ROMID_CRC:	.byte 1;ROM ID: CRC	m1Wts_spT:		.byte 2;scratch pad: Temperature	m1Wts_spTH:		.byte 1;scratch pad: Thigh alarm thd	m1Wts_spTL:		.byte 1;scratch pad: Tlow alarm thd	m1Wts_spCFG:	.byte 1;scratch pad: sensor config shadow	m1Wts_spRes:	.byte 3;scratch pad: reserve	m1Wts_spCRC:	.byte 1;scratch pad: scratch pad CRC_1Wts_sizeof:		.byte 0.nooverlap

... i w ten sposób próbuję nakłonić Cię do spróbowania 1-Wire :)

pozdro.

Udostępnij tę odpowiedź


Odnośnik do odpowiedzi
Udostępnij na innych stronach

Naprawdę nieźle to wygląda, nie wiem czy sobie poradzę z 1-Wire, ale spróbuję, a jakby coś to będę zakładał milion tematów ;)

 

Masz gdzieś może swoje kody udostępnione? Ciężko mi cokolwiek fajnego znaleźć w necie, żeby poczytać i nabrać takiej wprawy jak powinno się pisać w assemblerze, żeby się nie pogubić. W przypadku kalkulatora tak było, w końcu nie wiedziałem już których rejestrów używam w danej procedurze, których nie, generalnie tragedia.

 

Zauważyłem właśnie, że pisząc sobie procedurki, np do wyświetlenia bajtu na LCD później często gęsto jej używałem, co przypomina mi trochę C :) Długo już programujesz w asm, że takie rzeczy skomplikowane piszesz? ;)

 

Kolejną rzeczą jest, napisałeś, że ATmega32 ma dużo pamięci. Ja podczas pisania kalkulatora korzystałem tylko z rejestrów Rxx, przez co wydało mi się, że tej pamięci ciągle mi brakuje, bo w dwóch rejestrach trzymam ARG1, w dwóch ARG2, jakieś kopie jeszcze itd i zostaje mi kilka rejestrów, a nie wszystkie procedury mogą działać na pełnej palecie rejestrów, dużo z nich na xx>16.

 

Pewnie to przez brak doświadczenia:)

Udostępnij tę odpowiedź


Odnośnik do odpowiedzi
Udostępnij na innych stronach

Gość <account_deleted>

W przypadku kalkulatora tak było, w końcu nie wiedziałem już których rejestrów używam w danej procedurze, których nie, generalnie tragedia.

 

Zauważyłem właśnie, że pisząc sobie procedurki, np do wyświetlenia bajtu na LCD później często gęsto jej używałem, co przypomina mi trochę C :)

 

Kolejną rzeczą jest, napisałeś, że ATmega32 ma dużo pamięci. Ja podczas pisania kalkulatora korzystałem tylko z rejestrów Rxx, przez co wydało mi się, że tej pamięci ciągle mi brakuje, bo w dwóch rejestrach trzymam ARG1, w dwóch ARG2, jakieś kopie jeszcze itd i zostaje mi kilka rejestrów, a nie wszystkie procedury mogą działać na pełnej palecie rejestrów, dużo z nich na xx>16.

Czyste C to tak na prawdę asm, w którym wszystkio zostało zastąpine aliasami ;) Dopiero takie rozwinięcia jak klasy dają absurdalny poziom abstrakcji :lol: Ale co innego chciałem powiedzieć: używaj symboli/aliasów gdzie się da - i nie żałuj literek - nazwy powinny być jasne i najlepiej "strukturalne" np. w poprzednim datatajpie są takie prefixy: m1Wts = memory+1-wire+thermal_sensor, b1Wts=bit+..., r1Wts oznaczałoby rejestr, c=constant (stała -> daje to dużą przejrzystość i łatwo zapamiętać.

 

rcall to nic innego jak wywołanie funkcji w C - compilatory C robią dokładnie to samo, tylko trochę śmiecia dodają od siebie przez co jest gorzej.

 

Rejestry to poprostu zmienne - ponieważ ich ilość jest skończona (32) to w przypadku większych projektów trzeba poprostu zrzucać ich zawartość do RAMu (albo na stos - zależnie od konkretnej sytuacji) Zrzucenie rejestru do RAM to banał (sts, std, push) znacznie ważniejsze jest jasne zdefiniowanie struktur do przechowywania danych, dzięki czemu odwołujesz się do nich poprzez nazwy symboliczne: np:

 

LOAD Z,first_1W_sensor ;(load to macro, z=adres bazowy struktury w RAM)

ldd tmp,z+m1Wts_ROMID_FC ;(tmp= jakiś rejestr lokalny w funkcji, adresowanie pośrednie: Z-pointer + offset )

 

ps. kodów nigdzie nie udostępniam, ale jak będziesz potrzebował, to pomogę ;)

Udostępnij tę odpowiedź


Odnośnik do odpowiedzi
Udostępnij na innych stronach

Dołącz do dyskusji

Możesz dodać zawartość już teraz a zarejestrować się później. Jeśli posiadasz już konto, zaloguj się aby dodać zawartość za jego pomocą.

Gość
Dodaj odpowiedź do tematu...

×   Wklejono zawartość z formatowaniem.   Przywróć formatowanie

  Dozwolonych jest tylko 75 emoji.

×   Odnośnik został automatycznie osadzony.   Przywróć wyświetlanie jako odnośnik

×   Przywrócono poprzednią zawartość.   Wyczyść edytor

×   Nie możesz bezpośrednio wkleić grafiki. Dodaj lub załącz grafiki z adresu URL.

Ładowanie


×
×
  • Dodaj nową pozycję...