moszeusz Napisano Listopad 9, 2023 Udostępnij Napisano Listopad 9, 2023 Cześć, przygotowałem sobie kod, który coś tam mierzy na zewnętrznym ADC, mierzy temperaturę, robi obliczenia i wyświetla na wyświetlaczu TFT. Chciałem, aby różne funkcje w tym programie były wykonywane z różnymi interwałami czasowymi. Na początku próbowałem z millis(), ale wszystkie funkcje były wykonywane z interwałem 1-3 sekund, niezależnie co sobie ustawiłem. Potem wykorzystałem bilbliotekę TickerTwo - efekt jest tożsamy. Zrobiłem testowo dwie funkcje wyświetlające napis w monitorze portu i ustawiłem, żeby jedna wykonywała się co 500 ms, druga co 1000 ms i jeżeli są tylko te dwie funkcje uruchomione to jako tako działa, jak dodam pozostałe - wszystko się psuje. Proszę o pomoc. #include<ADS1115_WE.h> #include<Wire.h> #include <Adafruit_GFX.h> #include <DallasTemperature.h> #include <OneWire.h> #include <MD_AD9833.h> #include <SPI.h> #include <TickTwo.h> #include "Ucglib.h" #define DATA 25 ///< SPI Data pin number #define CLK 26 ///< SPI Clock pin number #define FSYNC 27 ///< SPI Load pin number (FSYNC in AD9833 usage) #define ONE_WIRE_BUS 14 #define I2C_ADDRESS 0x48 const int cd= 33; const int cs= 32; OneWire oneWire(ONE_WIRE_BUS); DallasTemperature sensors(&oneWire); MD_AD9833 AD(DATA, CLK, FSYNC); // Arbitrary SPI pins Ucglib_ST7735_18x128x160_HWSPI ucg(cd, cs); struct Wyniki_pH { float pH; float pH_kor; }; const float ph1 = 6.86; const float ph2 = 4.01; const float U1 = 8.8; const float U2 = 174.3; const float t_kal = 297.0; float s = (ph1-ph2)/(U1-U2); float b = ph1-s*U1; ADS1115_WE adc = ADS1115_WE(I2C_ADDRESS); Wyniki_pH pehametr(); float u_pehametr(); float u_konduktometr(); float u_bateria(); float temp(); void tft(); void serial1000(); void serial500(); TickTwo timer1(u_pehametr, 200); TickTwo timer2(u_konduktometr, 200); TickTwo timer3(u_bateria, 20000); TickTwo timer4(temp, 1000); TickTwo timer5(pehametr, 500); TickTwo timer6(tft, 500); TickTwo timer7(serial500, 500); TickTwo timer8(serial1000, 1000); void setup() { Wire.begin(); Serial.begin(9600); timer1.start(); timer2.start(); timer3.start(); timer4.start(); timer5.start(); timer6.start(); timer7.start(); timer8.start(); ucg.begin(UCG_FONT_MODE_TRANSPARENT); ucg.setColor(0, 120, 0, 0); ucg.drawBox(0, 0, ucg.getWidth(), ucg.getHeight()); adc.setVoltageRange_mV(ADS1115_RANGE_1024); //comment line/change parameter to change range adc.setCompareChannels(ADS1115_COMP_0_3); //comment line/change parameter to change channel adc.setCompareChannels(ADS1115_COMP_1_3); adc.setCompareChannels(ADS1115_COMP_2_3); adc.setConvRate(ADS1115_8_SPS); //uncomment if you want to change the default adc.setMeasureMode(ADS1115_CONTINUOUS); //comment line/change parameter to change mode AD.begin(); AD.setFrequency(MD_AD9833::CHAN_0, 13000); sensors.setWaitForConversion(0); sensors.setResolution(12); sensors.begin(); } float u_konduktometr(){ float u_kond=0.0; adc.setCompareChannels(ADS1115_COMP_2_3); u_kond = adc.getResult_mV(); return u_kond; } float u_pehametr(){ float u_ph=0.0; adc.setCompareChannels(ADS1115_COMP_0_3); u_ph = adc.getResult_mV(); return u_ph; } float u_bateria(){ float u_batt=0.0; adc.setCompareChannels(ADS1115_COMP_1_3); u_batt = adc.getResult_mV(); return u_batt; } float temp() { sensors.requestTemperatures(); float t = sensors.getTempCByIndex(0); return t; } Wyniki_pH pehametr() { Wyniki_pH wyniki; float a = u_pehametr(); float t = temp(); float k = 273 + t; float pH = s * a + b; wyniki.pH = pH; wyniki.pH_kor = pH + a / 0.19845 / t_kal - a / 0.19845 / k; return wyniki; } void serial500(){ Serial.println("test 500 ms"); } void serial1000(){ Serial.println("test 1000 ms"); } void tft() { Wyniki_pH wyniki = pehametr(); float pH = wyniki.pH; float pH_kor = wyniki.pH_kor; float a = u_pehametr(); float t = temp(); float c = u_konduktometr(); float d = u_bateria(); ucg_int_t y = 0; ucg_int_t h = 14; int x = 4; y += h; ucg.setColor(0, 120, 0, 0); ucg.drawBox(x, 2, 50, 14); ucg.setColor(0, 255, 255, 255); ucg.setFontMode(UCG_FONT_MODE_TRANSPARENT); ucg.setPrintPos(x,y); ucg.setFont(ucg_font_helvB08_tr); ucg.print(a,1); ucg.setPrintPos(60,y); ucg.print(" mV"); y += h; ucg.setColor(0, 120, 0, 0); ucg.drawBox(x, 16, 50, 14); ucg.setColor(0, 255, 255, 255); ucg.setFontMode(UCG_FONT_MODE_TRANSPARENT); ucg.setPrintPos(x,y); ucg.setFont(ucg_font_helvB08_tr); ucg.print(pH,2); ucg.setPrintPos(60,y); ucg.print(" = pH"); y += h; ucg.setColor(0, 120, 0, 0); ucg.drawBox(x, 30, 50, 14); ucg.setColor(0, 255, 255, 255); ucg.setFontMode(UCG_FONT_MODE_TRANSPARENT); ucg.setPrintPos(x,y); ucg.setFont(ucg_font_helvB08_tr); ucg.print(c,1); ucg.setPrintPos(60,y); ucg.print("mV = U k"); y += h; ucg.setColor(0, 120, 0, 0); ucg.drawBox(x, 44, 50, 14); ucg.setColor(0, 255, 255, 255); ucg.setFontMode(UCG_FONT_MODE_TRANSPARENT); ucg.setPrintPos(x,y); ucg.setFont(ucg_font_helvB08_tr); ucg.print(d); ucg.setPrintPos(60,y); ucg.print("mV batt"); } void loop() { timer1.update(); timer2.update(); timer3.update(); timer4.update(); timer5.update(); timer6.update(); timer7.update(); timer8.update(); }
kaczakat Listopad 9, 2023 Udostępnij Listopad 9, 2023 (edytowany) Na początek zamień to: float temp() { float t = sensors.getTempCByIndex(0); sensors.requestTemperatures(); return t; } Jak wywołujesz funkcję to odczytujesz temperaturę zmierzoną w poprzednim odczycie, potem zlecasz kolejny pomiar, by za pierwszym razem coś odczytał możesz zlecić pomiar i dopiero po nim ustawić setWaitForConversion 0. Jeszcze lepiej rozbić to na dwie funkcje, każda z nich zajmuje uC na 15ms, razem zajmują go w tej samej chwili na 30, jak je rozbijesz to o połowę zmniejszysz błąd innych timerów. Można tę funkcję wywoływać co 100ms, zlecasz pomiar w pierwszym wywołaniu, przez kolejne 8 wywołań nic nie robisz, czytasz temperaturę, w kolejnym zlecasz pomiar, zerujesz licznik wywołań i cykl startuje od nowa. Sprawdź też ile zajmują inne funkcje, użyj podobnej do millis funkcji micros(), złap czas przed funkcją, po funkcji, wydrukuj sobie różnice. Jeśli funkcje w sumie zajmą Ci 1s to ustawianiu różnych wywołań co 200-500ms jest tylko pobożnym życzeniem. 1wire i i2C zajmują stosunkowo dużo czasu. A poza tym wywołujesz taką funkcję temp() co 1s, a potem wg timera TFT znowu w funkcji TFT, to bez sensu. Możesz sobie ustawić setWaitForConversion(0) jeśli sam dbasz o to by przy 12 bitach nie zlecać pomiarów i odczytywać częściej niż 750ms, zaokrąglając co 1s. Edytowano Listopad 9, 2023 przez kaczakat literówka
jand Listopad 10, 2023 Udostępnij Listopad 10, 2023 W Twoim programie wartości obliczone przez funkcje, które są wywołane przez ticker nie mają możliwości być przekazanymi do programu głównego. Wygląda mi na to, że spodziewasz się że w funkcji pehametr(), w linijce float a = u_pehametr(); pod zmienną a zostanie podstawiona wartość uprzednio wyliczona przez u_pehametr() (w czasie jej wywołania przez ticker). Otóż nie, funkcja zostanie wywołana ponownie, a tamte wyliczenia nie mają znaczenia. Podobnie jest i w przypadku innych funkcji. Przepisz więc funkcje wywoływane przez ticker tak, by były typu void, a wyliczone wartości przekazuj przez zmienne globalne.
moszeusz Listopad 10, 2023 Autor tematu Udostępnij Listopad 10, 2023 Dzięki za podpowiedzi, ale chyba jestem zbyt głupi na operowanie funkcjami. Umieściłem wszystko w loopie, skorzystałem z millis i działa dobrze. Może mało eleganckie, ale dla moich celów jest wystarczające. 1
Danyeru Listopad 10, 2023 Udostępnij Listopad 10, 2023 @moszeusz też kiedyś nie umiałem, a potem trzeba było ogarnąć i wyszło. Pokaż kod, pomożemy zrobić osobne funkcje.
moszeusz Listopad 10, 2023 Autor tematu Udostępnij Listopad 10, 2023 No to tak wygląda kod, który działa, tak jak chciałem: #include<ADS1115_WE.h> #include<Wire.h> #include <Adafruit_GFX.h> #include <DallasTemperature.h> #include <OneWire.h> #include <MD_AD9833.h> #include <SPI.h> #include "Ucglib.h" #define DATA 25 ///< SPI Data pin number #define CLK 26 ///< SPI Clock pin number #define FSYNC 27 ///< SPI Load pin number (FSYNC in AD9833 usage) #define ONE_WIRE_BUS 4 #define I2C_ADDRESS 0x48 const int cd= 33; const int cs= 32; unsigned long aktualnyczas = 0; unsigned long zapamietanyCzas1 = 0; unsigned long zapamietanyCzas2 = 0; unsigned long zapamietanyCzas3 = 0; unsigned long zapamietanyCzas4 = 0; unsigned long zapamietanyCzas5 = 0; OneWire oneWire(ONE_WIRE_BUS); DallasTemperature sensors(&oneWire); MD_AD9833 AD(DATA, CLK, FSYNC); // Arbitrary SPI pins Ucglib_ST7735_18x128x160_HWSPI ucg(cd, cs); const float ph1 = 6.86; const float ph2 = 4.01; const float U1 = 8.8; const float U2 = 174.3; const float t_kal = 297.0; float s = (ph1-ph2)/(U1-U2); float b = ph1-s*U1; float c=0; float d=0; float a=0; float e=0; float u_kond=0.0; float u_ph=0.0; float u_batt=0.0; float t = 0.0; float k = 0.0; float pH = 0.0; float pH_kor = 0.0; ADS1115_WE adc = ADS1115_WE(I2C_ADDRESS); void setup() { Wire.begin(); Serial.begin(9600); ucg.begin(UCG_FONT_MODE_TRANSPARENT); ucg.setColor(0, 0, 0, 0); ucg.drawBox(0, 0, ucg.getWidth(), ucg.getHeight()); adc.setVoltageRange_mV(ADS1115_RANGE_1024); //comment line/change parameter to change range adc.setCompareChannels(ADS1115_COMP_0_3); //comment line/change parameter to change channel adc.setCompareChannels(ADS1115_COMP_1_3); adc.setCompareChannels(ADS1115_COMP_2_3); adc.setConvRate(ADS1115_8_SPS); //uncomment if you want to change the default adc.setMeasureMode(ADS1115_CONTINUOUS); //comment line/change parameter to change mode AD.begin(); AD.setFrequency(MD_AD9833::CHAN_0, 13000); sensors.setWaitForConversion(0); sensors.setResolution(12); sensors.begin(); } void loop() { aktualnyczas = millis(); sensors.requestTemperatures(); float t = sensors.getTempCByIndex(0); if (aktualnyczas - zapamietanyCzas2 >= 300) {adc.setCompareChannels(ADS1115_COMP_0_3); u_ph = adc.getResult_mV(); zapamietanyCzas2 = aktualnyczas;} if (aktualnyczas - zapamietanyCzas3 >= 20000) {adc.setCompareChannels(ADS1115_COMP_1_3); u_batt = adc.getResult_mV(); zapamietanyCzas3 = aktualnyczas;} if (aktualnyczas - zapamietanyCzas4 >= 300) {adc.setCompareChannels(ADS1115_COMP_2_3); u_kond = adc.getResult_mV(); zapamietanyCzas4 = aktualnyczas;} a = u_ph; k = 273+t; pH = s*a+b ; pH_kor = pH+a/0.19845/t_kal-a/0.19845/k; if (aktualnyczas - zapamietanyCzas5 >= 300) {ucg_int_t y = 0; ucg_int_t h = 20; int x = 4; y += h; ucg.setColor(0, 255, 255, 255); ucg.setColor(1, 0, 0, 0); ucg.setFontMode(UCG_FONT_MODE_SOLID); ucg.setPrintPos(x,y); ucg.setFont(ucg_font_helvR12_hr); ucg.print(u_ph,1); ucg.print(" "); ucg.setPrintPos(60,y); ucg.print(" mV pH"); y += h; ucg.setColor(0, 255, 255, 255); ucg.setColor(1, 0, 0, 0); ucg.setFontMode(UCG_FONT_MODE_SOLID); ucg.setPrintPos(x,y); ucg.setFont(ucg_font_helvR12_hr); ucg.print(pH_kor,2); ucg.print(" "); ucg.setPrintPos(60,y); ucg.print(" pH"); y += h; ucg.setColor(0, 255, 255, 255); ucg.setColor(1, 0, 0, 0); ucg.setFontMode(UCG_FONT_MODE_SOLID); ucg.setPrintPos(x,y); ucg.setFont(ucg_font_helvR12_hr); ucg.print(u_kond,1); ucg.print(" "); ucg.setPrintPos(60,y); ucg.print("mV kond"); y += h; ucg.setColor(0, 255, 255, 255); ucg.setColor(1, 0, 0, 0); ucg.setFontMode(UCG_FONT_MODE_SOLID); ucg.setPrintPos(x,y); ucg.setFont(ucg_font_helvR12_hr); ucg.print(u_batt,1); ucg.print(" "); ucg.setPrintPos(60,y); ucg.print("mV batt"); y += h; ucg.setColor(0, 255, 255, 255); ucg.setColor(1, 0, 0, 0); ucg.setFontMode(UCG_FONT_MODE_SOLID); ucg.setPrintPos(x,y); ucg.setFont(ucg_font_helvR12_hr); ucg.print(t,1); ucg.print(" "); ucg.setPrintPos(60,y); ucg.print("st. C"); zapamietanyCzas5 = aktualnyczas;} }
jand Listopad 10, 2023 Udostępnij Listopad 10, 2023 A więc większość pracy już za nami . Korzystając z zamieszczonego programu, na przykładzie pierwszego odmierzanego w pętli loop() czasu pokażę jak to zrobić. Fragment if (aktualnyczas - zapamietanyCzas2 >= 300) {adc.setCompareChannels(ADS1115_COMP_0_3); u_ph = adc.getResult_mV(); zapamietanyCzas2 = aktualnyczas;} Przerabiamy na funkcję (oczywiście umieszczając ją poza loop()) void funkcja1 () { adc.setCompareChannels(ADS1115_COMP_0_3); u_ph = adc.getResult_mV(); } I dalej, jak już wiesz - na początku programu: #include <TickTwo.h> // tu inne rzeczy TickTwo timer1(funkcja1, 300); w setup() umieszczamy: timer1.start(); a do loop() w miejsce usuniętego fragmentu wstawiamy: timer1.update(); Pozostałe funkcje tworzymy podobnie.
kaczakat Listopad 11, 2023 Udostępnij Listopad 11, 2023 Ja wrzucę kawałek dla DS, możesz wgrać i zauważyć, że LED miga co 50ms bez przeszkód, wykorzystany jest switch case, o którym pisałem wcześniej. Przy mniejszych czasach migania led, 10-20ms można już gołym okiem zauważyć zakłócenie wprowadzane przez komunikację ONE WIRE. Ale można tego blinka wywołać i co 1ms, po prostu 2x wciągu jednej sekundy czknie mu się o te kilkanaście ms, no a po dodaniu reszty kodu o np. komunikację po i2c. Zmienna static działa jak globalna zachowując swoją ostatnią wartość do ponownego wywołania funkcji, ale jest dostępna tylko w tej funkcji. Dzięki temu taka funkcja może realizować wybrany fragment w zależności ile czasu upłynęło od zlecenia pomiaru, jeden case, a w każdym wywołaniu robi tylko zmianę tego licznika. #include <DallasTemperature.h> #include <OneWire.h> #include <TickTwo.h> #define ONE_WIRE_BUS 4 OneWire oneWire(ONE_WIRE_BUS); DallasTemperature sensors(&oneWire); float tempDS; void temp(); void serial1000(); void serial500(); void ledBlink(); TickTwo timer1(temp, 100); TickTwo timer2(serial500, 500); TickTwo timer3(serial1000, 1000); TickTwo timer4(ledBlink, 50); void setup() { Serial.begin(115200); timer1.start(); timer2.start(); timer3.start(); timer4.start(); sensors.setWaitForConversion(0); sensors.setResolution(12); sensors.begin(); } void loop() { timer1.update(); timer2.update(); timer3.update(); timer4.update(); } void temp() { static uint8_t ktoraSetkaSekundy=0; switch (ktoraSetkaSekundy) { case 0: sensors.requestTemperatures(); break; case 3: Serial.println("Kuku z trojki"); break; case 8: tempDS = sensors.getTempCByIndex(0); break; default: // gdy 1,2,3,4,5,6,7 i 9 nic ta funkcja nie robi poza ktoraSetkaSekundy++ break; } ktoraSetkaSekundy++; if( ktoraSetkaSekundy>9) ktoraSetkaSekundy=0; } void serial500() { Serial.println("test 500 ms"); } void serial1000() { Serial.print("Temperatura: "); Serial.println(tempDS); } void ledBlink() { static bool startujemy=0; if(! startujemy) { pinMode(LED_BUILTIN,OUTPUT); startujemy=1; } digitalWrite(LED_BUILTIN, ! digitalRead (LED_BUILTIN)); } 1
moszeusz Listopad 13, 2023 Autor tematu Udostępnij Listopad 13, 2023 Dzięki, zmodyfikowałem kod i działa. Natomiast rzeczywiście I2C komplikuje sprawę i niżej niż 500 ms nie jestem w stanie zejść. Dodatkowo również obsługa wyświetlacza wprowadza spore opóźnienie, ale nawet te 500 - 1000 ms w tym wypadku jest dla mnie akceptowalne. A z ciekawości - jest jakaś inna możliwość, żeby zejść z opóźnieniami do powiedzmy 100 ms przy wykorzystaniu I2C itd? Jakiś inny mikrokontroler, czy to po prostu jest fizycznie nie możliwe?
jand Listopad 13, 2023 Udostępnij Listopad 13, 2023 To nie jest wina ani kontrolera, ani szyny I2C. I2C może wykonywać tysiące operacji na sekundę.
moszeusz Listopad 13, 2023 Autor tematu Udostępnij Listopad 13, 2023 A czego to jest wina w takim wypadku? Usunąłem obsługę ADS1115 i się poprawiło. Pytam, bo chciałbym zrozumieć o co może chodzić
jand Listopad 13, 2023 Udostępnij Listopad 13, 2023 (edytowany) Układ ADS1115 ma programowane tempo próbkowania sygnału - od 8 do 860 próbek/sek. Jeśli masz ustawione na 8, to nic dziwnego, że trzeba czekać ok. 100ms na nowy wynik. Rozumiem, że korzystasz z gotowej biblioteki - więc pewnie trzeba się w nią trochę wgryźć i zrozumieć, jak działa (mniej więcej). Edytowano Listopad 13, 2023 przez jand
moszeusz Listopad 13, 2023 Autor tematu Udostępnij Listopad 13, 2023 Ok, ma to sens, dzięki za wyjaśnienia.
kaczakat Listopad 14, 2023 Udostępnij Listopad 14, 2023 3 godziny temu, jand napisał: I2C może wykonywać tysiące operacji na sekundę. Może zmieniać stan nawet 400 tys/s zmian stanu pinu, standardowo 100k, ale jak ma do wysłania 1kB to już zajmuje sporo ms, dlatego warto tak dobrać częstość komunikacji i wybór chwili jej użycia, szczególnie dla graficznych, by nie robić tego częściej niż jest to konieczne, zależy też co biblioteka umie. Przy jakimś odczycie ADC to powinny być us jednak, chyba, że tak jak w DS18B20 biblioteka zleca jeden pomiar, czeka na zakończenie i odczytuje. @moszeusz nie kojarzę czy podałeś uC, ale piny SPI nie pasują do AVR czy ESP32, jak to ma działać na softSPI to będzie mulić, to samo z I2C. Na pewno warto sprawdzić, czy biblioteka ustawia tryb 400kHz (Fast Mode) jeśli slave to akceptuje. Jak wyświetlacz jest SPI to też są różne opcje prędkości, dla sprzętowego SPI. Użyj micros(), zmierz po kolei ile co zajmuje, to będziesz wiedział co Ci blokuje. W tym programie "co Ci działa" na millis masz w każdym loop zlecenie pomiaru 1wire, jak to jest kod, nad którym pracujesz to nic dobrego z tego nie wyniknie. Dobra, wgrałem tą bibliotekę, akurat miałem taki ADC, domyślnie jest próbkowanie 128, zmieniłeś na 8, czas odczytu 1 kanału zmienia się z 18ms na 262ms, jak czytasz 3 to sobie zrób mnożenie. Albo możesz sobie zmierzyć: uint32_t start,stop,wynik; start=micros(); adc.setCompareChannels(ADS1115_COMP_0_3); u_ph = adc.getResult_mV(); adc.setCompareChannels(ADS1115_COMP_1_3); u_batt = adc.getResult_mV(); adc.setCompareChannels(ADS1115_COMP_2_3); u_kond = adc.getResult_mV(); stop=micros(); wynik=stop-start; Serial.println(wynik); Jeśli w setup zrobiłeś inicjalizację adc.setCompareChannels(ADS1115_COMP_0_3); to na pewno musisz to powtarzać przed każdym odczytem? Przy ustawieniu 475 czas odczytu 1 kanału to 8ms (i2c sprzętowy w UNO). Coś co dzieje się kilka ms to i tak długo, dlatego jak chcesz mieć odczyt co 1s wszystkich kanałów to możesz zrobić jak pokazałem w przykładzie z DS switch case, jedna funkcja do wszystkich kanałów, potem przykładowo dzielisz sekundę na 10 części, wywołujesz funkcję co 100ms, w wybranych case robisz odczyt jednego kanału, z maksymalną prędkością próbkowania jaka nie pogarsza dokładności. Możesz to też dodać do funkcji DS, masz tam dużo wolnych case i rozrzucić te czasochłonne czynności równomiernie po sekundzie. Możesz też się zastanowić, czy w ogóle potrzebujesz tak próbkować, może nie ma znaczenia co się zmieniło przez 5s, jak bym mierzył temperaturę w pokoju, to raz na powiedzmy 15s w zupełności by mi wystarczało, ale jak bym chciał reagować na przyciski to wolałbym odczytywać je co 10ms, by nie stać przed sterownikiem jak petent w urzędzie i czekać kiedy uC znajdzie dla mnie czas. Kolejnym krokiem może być użycie pinu alarmu, można zlecić pomiar i nie czekać na wynik, a ADC wywoła przerwanie na wybrany pin, że konwersja została zakończona, wtedy pojedynczy odczyt trwa 2.5ms, lub 1.3ms w trybie ADS1115_CONTINUOUS. Jest przykład w bibliotece. Przestawiłem jeszcze magistralę i2c na 400kHz, ale zeszło niewiele poniżej 1ms, to już czujnik musi mieć jakiegoś dużego pinga przy odpowiedzi. Ale zbierając wszystko do kupy możesz to przyspieszyć z 1000x na zwykłym UNO, masz nad czym popracować. 1
moszeusz Listopad 14, 2023 Autor tematu Udostępnij Listopad 14, 2023 Dzięki za kolejne podpowiedzi, jeżeli nie w tym projekcie to wykorzystam je w przyszłych.
Pomocna odpowiedź
Bądź aktywny - zaloguj się lub utwórz konto!
Tylko zarejestrowani użytkownicy mogą komentować zawartość tej strony
Utwórz konto w ~20 sekund!
Zarejestruj nowe konto, to proste!
Zarejestruj się »Zaloguj się
Posiadasz własne konto? Użyj go!
Zaloguj się »