Elektroda.pl
Elektroda.pl
X
Please add exception to AdBlock for elektroda.pl.
If you watch the ads, you support portal and users.

Zasady pisania programów pseudowielowątkowych na małych proc

janbernat 30 Sep 2010 23:47 14660 103
Computer Controls
  • Helpful post
    #61
    User removed account
    User removed account  
  • Computer Controls
  • #62
    nsvinc
    Level 35  
    Quote:
    2. «określić, oznaczyć coś z góry»
    ...heh...
    Nie było moim celem chamskie zagrywki.
    Raczej kontynuacją mojego toku myślenia - sam potwierdzałeś 100% deterministyczność kodu w C, więc nie powinny Cię zaboleć jakiekolwiek cytaty :)
    A cały czas chodziło tylko o kompilator, i to, jak zareaguje w pewnych rzadko spotykanych okolicznościach. Może masz rację, i ja się mylę, a może nie. Jak już próbowałem pokojowo rozwiązać sytuację - każdy programista widzi tematy inaczej. Kolega tmf ostatnio napisał, że przypadek, o którym dyskutujemy, jest jednym z wyjątków kiedy volatile nie jest konieczny - być może nie jest.
    I pomyśleć, że o jedno volatile powstała wojna na 30 postów....

    janbernat wrote:
    To czy z tego co pisze nsvinc program ma sobie "olać" starszą flagę czy też "grzebać w śmietniku" i znaleźć tę starszą i wykonać co ona chce a potem wykonać parę innych zadań i wykonać to czego chce "młodsza" flaga.

    Po kolei. Ale jeśli zdarzenia zależą od okoliczności, a ty ich nie zapamietujesz w kolejce, zdarzenia z kolejki mogą okazać się już nieaktualne lub niewłaściwe, w szczególności w przypadkach, gdy te zdarzenia karmią GUI lub inny interfejs komunikacji z uzytkownikiem. W momencie zmiany sposobu interpretacji zdarzeń (np. zmiany formatki) powinno się wtedy flushować kolejkę zdarzeń, lub po prostu nie dopuszczać do zmiany sposobu interpretacji zdarzeń dopóki nie będzie w kolejce zdarzeń dotyczących danego sposbu interpretacji (zależnej od np. formatki GUIa).


    PS.
    ->albertb
    Nie burz się - jakkolwiek byś nie napisał - kompilator nigdy nie przewidzi nic, a tylko zrobi co zechcesz - sam dałeś to do zrozumienia...Sorry :]
  • Computer Controls
  • #63
    janbernat
    Level 38  
    tmf wrote:

    janbernat wrote:

    Jeśli zrobię sobie FIFO- pierścieniowe albo nie- i będę do niego wrzucał zdarzenia- przerwania albo wyniki działań funkcji- to w tym rejestrze mogą się znaleźć zarówno stare jak i nowsze flagi.
    Dotyczące tego samego żądania obsługi.
    To czy z tego co pisze nsvinc program ma sobie "olać" starszą flagę czy też "grzebać w śmietniku" i znaleźć tę starszą i wykonać co ona chce a potem wykonać parę innych zadań i wykonać to czego chce "młodsza" flaga.


    Musi po kolei odczytywać zdarzenia i je kolejno realizować. W przeciwnym wypadku nie byłoby sensu implementacji FIFO, wystarczyłaby tylko jedna pozycja zawierająca aktualny stan flagi.


    nsvinc wrote:

    janbernat wrote:
    To czy z tego co pisze nsvinc program ma sobie "olać" starszą flagę czy też "grzebać w śmietniku" i znaleźć tę starszą i wykonać co ona chce a potem wykonać parę innych zadań i wykonać to czego chce "młodsza" flaga.

    Po kolei. Ale jeśli zdarzenia zależą od okoliczności, a ty ich nie zapamietujesz w kolejce, zdarzenia z kolejki mogą okazać się już nieaktualne lub niewłaściwe, w szczególności w przypadkach, gdy te zdarzenia karmią GUI lub inny interfejs komunikacji z uzytkownikiem. W momencie zmiany sposobu interpretacji zdarzeń (np. zmiany formatki) powinno się wtedy flushować kolejkę zdarzeń, lub po prostu nie dopuszczać do zmiany sposobu interpretacji zdarzeń dopóki nie będzie w kolejce zdarzeń dotyczących danego sposbu interpretacji (zależnej od np. formatki GUIa).

    No ale zdrzenia zawsze zależą od okoliczności- taka ich natura.
    I co to znaczy- nie zapamiętujesz w kolejce?
    To chyba muszą być zapamiętane gdzie indziej- ale gdzieś zapisane.
    Te dwie powyższe interpretacje chyba się wykluczają.
    nsvinc- czu mógłbyś używać mniej wyszukanego żargonu?
    Podobno zagadnienie dopiero wtedy się naprawdę rozumie gdy potrafi się je wytłumaczyć komuś "zielonemu" używając zrozumiałych dla "zielonego" terminów.
    Poza tym zmartwiło mnie Twoje twierdzenie że C może działać inaczej zależnie od platformy.
    Oczywiście abstrahując od takich spraw jak peryferia, wielkość zmiennych itp.
    Bo wcale nie miałem zamiaru uczyć się języka nieobliczalnego w działaniu.
    Ale z kolei Freddy Chopin pocieszył mnie że C jest przewidywalny.
    Że działa tak samo na Pentium jak i na Attiny13.
    Więc może czas na naukę C nie nie jest tak całkiem zmarnowany.
  • Helpful post
    #64
    tmf
    Moderator of Microcontroller designs
    Interpretacja zdarzeń należy do ciebie, tylko ty wiesz czy możesz jakieś pominąć, czy też nie. Np. robisz licznik na enkoderze. Każde przekręcenie enkodera ma go zmniejszać/zwiększać, generuje więc zdarzenie. W innym miejscu je odbierasz i zmieniasz stosownie licznik. W takiej sytuacji analizujesz zdarzenie po zdarzeniu. Inny przykład - masz kolejkę komunikatów generowanych przez GUI. Kolejne komunikaty np. RePaint zostały wygenerowane dla tego samego obiektu. Oczywiście zamiast 10 razy przemalowywać obiekt, możesz to zrobić raz a pozostałe z kolejki usunąć.
    Co do przewidywalności języka C - jest przewidywalny. Ale są pewne granice. Są one wyznaczone przez zależną od platformy implementacje pewnych funkcji - np. register, volatile,interrupt itp. Stąd korzystając z rozszerzeń poza standard, a programując mikrokontrolery zawsze z takich rozszerzeń musisz korzystać, trzeba się zorientować jak pewne opcje czy atrybuty na konkretnej platformie są interpretowane. Stąd ten sam kod może wykazywać daleko idące różnice w działaniu na różnych platformach.
  • #65
    janbernat
    Level 38  
    Zaraz- volatile chyba jest w standardzie.
    Że register może kompilator uwzględnić albo nie- też czytałem.
    Najgorzej z interrupt.
    W podręcznikach- nic nie ma.
    A z kolei gdy C był tworzony to przecież przerwania były w każdym procesorze.
    No ale trudno- jakoś przerwania i ich obsługę to jestem w stanie zrozumieć.
    No ale if(), while() i + to może działa jednakowo. :cry:
    P.S.
    Trochę rozumiem- a trochę mój ciasny techniczny umysł protestuje- jak można pominąć pewne zdarzenia- i obsłużyć tylko ostatnie.
    Ale to może dlatego że procesor- i program do niego- stosuję tylko do obsługi urządzeń technicznych- silnik, enkoder, transmisja itp.
    O żadnych GUI oszukujących percepcję ludzkiego oka jeszcze nie myślałem.
    Jakbym tak chciał oszukać maszynę- toby mi dopiero dała.
    Frezy, stemple połamane- i wogóle same nieszczęścia.
    No ale fakt- procesory nie tylko do istotnych zadań służą.
    Czasem tylko do wyświetlania obrazków.
  • Helpful post
    #66
    sulfur
    Level 24  
    janbernat: ale jaki jest problem z volatile ? register to jest raczej sugestia dla kompilatora. Niemożliwe jest obligatoryjne trzymanie zmiennej w rejestrze. Dlaczego najgorzej jest z interrupt ? Naczytał się kolega stek bzdur i teraz zdaje się mieć wątpliwości co do sensu programowania w C. Zapewniam, że nie jest tak źle.

    Co do kolejki/buforu cyklicznego/innych tego typu wynalazków. Zaprezentował kolega w pierwszym poście kod, który się wykonuje zależnie od flagi ustawionej przez przerwanie. A teraz co zrobić, jeśli efektem pracy przerwania jest jakaś wartość, którą trzeba przekazać do pętli głównej ? Coś jak argument funkcji. Można to zrealizować przez zdefiniowanie dwóch indeksów i jakiejś tablicy na dane.

    char tab[16], indexisr, indexpg;
    
    ISR()
    {
      tab[indexisr++] = jakaswartosc;
    }
    
    main()
    {
      while(1)
      {
    // tu jakis inny kod obslugi czegokolwiek
    
        while(indexisr != indexpg)
        {
           LCD_WriteData(tab[indexpg])
           indexpg++;
        }
    
    // tu jakis inny kod obslugi jeszcze czegos innego
      }
    }
    To tak w pseudokodzie. Pisane na kolanie. Należy pamiętać jeszcze, że jak się doliczy do 15 to następne powinno być zero, że indexisr nie może wyprzedzić indexpg i kilka takich implementacyjnych szczegółów.
  • #67
    janbernat
    Level 38  
    Nareszcie mam coś nad czym mogę myśleć.
    Ale muszę to zrobić w prawdziwym programie- nawet jeśli to ma być sztuczny twór służący wyłącznie do sprawdzenia.
    Ale muszę zupełnie zmienić swój sposób myślenia.
  • Helpful post
    #68
    sulfur
    Level 24  
    Załóżmy, że otrzymaliśmy trzy "komunikaty". Wszystkie w jakimś stopniu modyfikują dane wyświetlane na LCD. Możemy zmieniać tekst na LCD każdym komunikatem osobno, lub przejechać przez trzy komunikaty, które swoje zmiany wpisują w tablicę znaków (char), która potem ląduje w LCD_WriteText (analogicznie z niedawnego tematu o itoa i różnicach pomiędzy moim kodem a kolegi tymon_x). Napisałbym, że nic pomijać nie można, ale zaraz w tym temacie zrobiłaby się awantura na kolejne dwie strony. Ty to projektujesz, Ty zatem decydujesz, czy, co i ewentualnie kiedy możesz pominąć. Czasem się da, czasem nie, a czasem się dla, ale się nie opłaca.

    Jeśli chodzi o kolegów posługujących się tego typu przykładami: Panowie, to jest dział o mikrokontrolerach i nie każdy programował w życiu jakieś okienka w WinAPI czy innych, zatem umówmy się, że nie odwołujemy się do takich przykładów.
  • Helpful post
    #69
    tmf
    Moderator of Microcontroller designs
    Kolega sulfur nie doczytał, nie pisałem o WinAPI, tylko o swoim okienkowym API na AVR, a że jest podobny do WinAPI z czasów W3 to nie dziwne.
    Pominąć zdarzenia możesz nie tylko w przypadku okienek, także w technicznych zastosowaniach. Np. czekasz na naciśnięcie przycisku, nerwowy użytkownik naciska go 3 razy, ale ciebie interesuje tylko fakt, że go nacisnął, po co więc 3 komunikaty o tym, wystarczy jeden. Ale żebyś miał coś do poduchy, oto kod mojej kolejki komunikatów:
    
    TMFEvent *TMFApp::msg[MESSAGE_QUEUE];
    uint8_t TMFApp::Queue_Begin;
    uint8_t TMFApp::Queue_End;
    
    bool TMFApp::PutMessage(TMFEvent *e)
    {
    	bool ret=false;
    
    	ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
    	{
        	uint8_t qe=(Queue_End+1) % MESSAGE_QUEUE;
    		uint8_t qb=Queue_Begin;
    
    		uint8_t ql=(Queue_End-1) % MESSAGE_QUEUE;
    		if(!((Queue_Begin!=Queue_End) && (memcmp(e, msg[ql], sizeof(TMFEvent))==0)))    //Don't insert message if it duplicates last message in queue
                if(qe!=qb)                                  //No space in queue
                {
                    msg[Queue_End]=e;
                    Queue_End=qe;
                    ret=true;
                    TMFPCCOMPAT_UPDATE_EVENTS(e, Queue_Begin,Queue_End)
                }
    	}
        return ret;
    }
    
    TMFEvent *TMFApp::GetMessage()
    {
    	TMFEvent *e=NULL;
    
    	ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
    	{
        	if(Queue_Begin!=Queue_End)
    		{
                TMFPCCOMPAT_UPDATE_EVENTS(e, Queue_Begin,Queue_End)
    
          		uint8_t q=Queue_Begin;
          		Queue_Begin=(Queue_Begin+1) % MESSAGE_QUEUE;
    	  		e=msg[q];
    		}
        }
        return e;
    }
    
    void TMFApp::DispatchMessage()
    {
        TMFEvent *te=GetMessage();
        if(te)
        {
    
    #ifdef INPUT_RotaryEncoder
    		if(te->msg==CMD_Encoder)	//Check user activity to check if LCD and LCD backlight should be turned on
    		{
    			LCD->SetLCDOn(LCDDriver::LCD_FullPower);
    			ETimer::Instance().PostProneEvent(InactivityTimer, LCD_BACKLIGHT_OFF);
    
    			if((te->intval & (ENC_Pressed | ENC_JustReleased | ENC_JustPressed))==0)	//User rotated encoder without pressing it
    			{
    				TMFObject *topobj=ChangeFocus(te->byteval, GetList());
    				SetList(topobj);    //In case top object changed
    				delete te;
    				return;
    			}
    		}
    #endif
    
    		if((te->msg==CMD_TimerEvent) && (te->param==InactivityTimer))	//Check if it is Timer message directed to TMFApp object
    		{
    			LCD->SetLCDOn(LCDDriver::LCD_LEDOff);	//Turn off LCD backlight
    			delete te;
    			return;
    		}
    
            if(te->receiver)
    		{
                bool ret=false;
    
                if(te->msg==CMD_Close) Close(te->receiver);
                 else
                {
                    TMFObject *tmpobj=te->receiver;
    
                    while((tmpobj) && ((ret=tmpobj->DispatchMessage(te))==false))   //If message wasn't handled try if parent object can handle it
                    {
                        tmpobj=tmpobj->GetParent();
                    }
                }
    		}
                else
            {
    			if(GetList()) DispatchMessage(te,GetList());
            }
    
            delete te;
        }
    }
    
    void TMFApp::DispatchMessage(TMFEvent *e, TMFObject *obj)
    {
    	if(obj->GetNext()) DispatchMessage(e, (TMFObject*)obj->GetNext());
    	else
    	{
    		if(IsAllChildsDisabled(obj)==false) DispatchMessage(e, (TMFObject*)obj->GetChild());
    		else
    		{
    			obj->DispatchMessage(e);
    		}
    	}
    }
    
    


    Masz tu dwie metody do wkłądania i pobierania komunikatów z kolejki oraz dispatcher rozsyłający komunikaty do odpowiednich obiektów. Przy okazji następuje proste usuwanie identycznych komunikatów.
  • #70
    janbernat
    Level 38  
    Dzięki- ja nie chcę programować okienek w w windzie.
    Wcale nie chcę się uczyć programowania na PC.
    Mam dzieci- zawodowych programistów- oni to zrobią 100 razy szybciej i 1000 razy lepiej niż ja.
    Ale nic mi nie pomogą gdy opowiadam im o konfiguracji rejestrów w mikroprocesorach.
    Robią Wielkie Oczy- Tata- w tak niskim poziomie to nikt nie programuje.
    Przecież Ty chcesz się dogrzebać do krzemu- tak nikt nie robi.
    P.S.
    Ciężka ta poducha się robi- ale jest nad czym myśleć.

    Dodano po 21 [minuty]:

    A bool to w gnu99 jest?
    Bo już głupieję- dość szybko.
  • #71
    mirekk36
    Level 42  
    janbernat wrote:

    Bo już głupieję- dość szybko.


    No nie dziwię się, ja też bym zgłupiał ;) poważnie....

    Tak sobie poczytałem cały ten wątek i aż mi ciarki po plecach przeszły od tej niby-wojny ;) tymczasem wyszedł z tego bełkot dla kogoś kto zaczyna naukę C. (tzn proszę mnie źle nie zrozumieć, nie chcę nikomu dogadywać)

    Pytanie wpierwszym poście było proste aczkolwiek nie do końca precyzyjne bo to co dla autora jest małym prockiem dla innych bywa już nie wiadomo zaraz jakim ARM'em. No i dzięki temu się zaczęło od małych procków a skończyło prawie na windowsie nie mówiąc już o pomieszaniu z poplątaniem języków bo i asembler i C ale także jak w ostatnim przypadku zaraz C++. Ja bym trochę sprecyzował takie pytanie dodając, że np chodzi o język C i np o procki AVR. To od razu uniknęłoby się tej sporej ilości podpowiedzi o prockach z wieloma stosami , o asemblerze i C++.

    Generalnie janberant idziesz w dobrym kierunku podając tą swoją wersję programu nazwijmy go wielowątkowego. (A to, że zaraz rozpoczyna się dyskusja akademicka na temat co to jest wielowątkowość a czym nie jest to można sobie odpuścić - bo tacy zawsze się znajdą, muszą pogadać) Czyż nie chodzi o to, żeby np w tym samym czasie jakiś nawet "biedny" AVR mógł np:

    1. wyświetlać coś na LCD (kilka różnych informacji niezależnych np w czetrech narożnikach)
    2. zajmować się multipleksowaniem 4-6 wyświetlaczy 7-segm. LED
    3. obsługiwać z 10 czujników DS18x20 na 1wire
    4. obsługiwać pilota IR
    5. obsługiwać dowolnie np 1-6 klawiszy i to jeszcze z funkcją REPEAT
    6. komunikować się przez UART
    7. obsługiwać RTC i EEPROM przez I2C
    8. dokonywać pomiarów ADC z ładnym uśrednianiem

    no i żeby nic a nic nawet na ociupinkę nie zakłócało siebie nawzajem ;)

    ja odpowiem że MOŻNA i to bez problemu. Oczywiście można na wiele sposobów. Jednym z nich jest oczywiście organizacja kolejki FIFO dla komunikatów i ich obsługa, ale można to spokojnie zrobić w zwykłym C. Tyle, że można także inaczej. Oczywiście prawdą też jest , że rozwiązań będzie tyle ilu programistów. I nie zarzucam nikomu w tych swoich początkowych słowach, że czegoś nie wie albo gada głupoty. Gorzej jest tylko z opisaniem tego co się robi i stąd takie dysputy.

    Tym drugim poza komunikatami sposobem jest wg mnie wydzielenie w programie procesów i zdarzeń. Ty janbernat to co zrobiłeś w pierwszym poście, to opisałeś tylko jakby zdarzenia, sygnalizowane w pętli poprzez jakieś flagi z różnych przerwań i jest to jak mówiłem dobry kierunek. Ale nie potrzeba zaraz wszystkiego co się da opierać o przerwania, czyli jak tu wielu pisało, w przerwaniach obsługa 1wire, w przerwaniach obsługa klawiatury czy klawiszy, w przerwaniach to... i w przerwaniach tamto... (tzn można ale wg mnie jest prostszy i przyjemniejszy sposób) ... kontynuując - masz już załatwione zdarzenia, ale np takie proste rzeczy jak obsługę klawiszy itp nie klep w przerwaniach.

    za to zrób sobie na JEDNYM timerze sprzętowym tzw "tyknięcia systemowe" odmierzające jakąś podstawę czasu np 10ms (zwykle wstarcza) i teraz wykorzystując np dzielenie modulo (albo jak chcesz), obliczaj wygodne dla każdego z procesów odcinki czasu w jakich mają być wykonywane. Będzie to tzw system karuzelowy. W najprostszej wersji bez ustawiania żadnego priorytetu dla poszczególnych procesów będą one odpalane jeden po drugim, chociaż np jeden może się wykonywać co 500ms a inny co 40ms a jeszcze inny co kilka sekund, a część procesów jak np obsługa klawiszy w sposób ciągły czyli z rozdzielczością co 10ms. Tylko teraz cała sztuka polega na tym aby wszystkie procesy i obsługa zdarzeń działały bez wprowadzania żadnych opóźnień poza czasem wykonania własnego koniecznego kodu. ŻADNYCH _delay_xx() w ramach ich działań ;) Przy czym ta "sztuka" o której napisałem wcale nie jest taka trudna jeśli masz coś takiego już pod ręką jak "sys ticks" dostępne dla każdego procesu. Do takich zastosowań ten sposób nadaje się idealnie a napisanie kodu nie graniczy z cudem nawet dla początkującego.

    Wtedy przerwania będą w większości służyły do konkretnych celów a nie wrzucania w nie hektolitrów kodu do obsłgi tego co można obsłużyć w takich procesach karuzelowo.

    Wtedy np wyświetlanie na LCD nie będzie musiało być jakoś tam pouzależniane od czegokolwiek. Ot każdy proces, który ma taką wewnętrzną potrzebę to zrobi swoje na tym czy innym wyświetlaczu A biorąc pod uwagę, że mając tak małą rozdzielczość jak 10ms to można spokojnie zastosować pełne buforowanie dla wyświetlania na LCD, i np co 40ms wysyłać do niego cały bufor np 2x16 znaków. Operacja szybka i skuteczna. Natomiast wszystkie procesy będą pracować jedynie na własnych ramkach/okienkach. Dzięki czemu dany proces może "przykryć" wyświetlacz swoimi danymi po czym gdy już nie trzeba zabiera swoje okienko i znowu się pokazuje to które było widać przedtem (czyli inną ramkę procesu, który ładnie wświetla tam cały czas godzinę, datę, temperaturę - nawet wtedy gdy jego ramka nie jest w danym momencie pokazywana bo inny proces swoją wstawił wyżej - no ale to już dochodzimy prawie do systemu okienkowego ;) ... ale tak np dokładnie działa program LCD Smartie - do sterowania wyświetlaczem LCD z kompa.
  • #72
    tmf
    Moderator of Microcontroller designs
    Nie wiem co się tak wszyscy rzucili na to nieszczęsne WinAPI i PC - mój przykład to fragment programu na AVR. Pochodzi z biblioteki obsługującej okienkowe GUI na AVR. I ilustruje to o co chodzi autorowi postu - bufor pierścieniowy do realizacji kolejki komunikatów. A że jest w C++ - no cóż, przy takich problemach C++ wydaje się naturalnym wyborem - mamy komunikaty, mamy więc obiekty do których trafiają. A że da się to zrobić w C - da się, tylko po co? W asemblerze też się da... A że tak naprawdę poświęcimy sporo czasu na implementację funkcji dostępnych w C++ od reki to inna sprawa.
  • #73
    sulfur
    Level 24  
    mirekk36: w pierwszym poście nie było pytania.

    tmf: to nie była raczej kwestia doczytania, tylko zrozumienia. Wyszło na to, że źle zinterpretowałem zagadnienie opisywane przez kolegę. Inna sprawa, że nie wiedziałem, że ktoś "bawi się" w takie komunikaty na AVRach.
  • Helpful post
    #74
    michalko12
    MCUs specialist
    janbernat wrote:

    A bool to w gnu99 jest?
    Bo już głupieję- dość szybko.


    Jak sobie napiszesz to będzie:
    
    #ifndef true
    #define true 1
    #endif
    
    #ifndef false
    #define false 0
    #endif
    
    
    typedef unsigned char bool;



    i już masz bool
  • #75
    mirekk36
    Level 42  
    sulfur wrote:
    mirekk36: w pierwszym poście nie było pytania.


    Ja tam widzę pytanie, tyle że na zbyt dużym poziomie abstrakcji co spowodowało, że każdy opisywał swoje doświadczenia z różnych platform, z implementacji w różnych językach i do tego przekonywał że jego najlepsze

    sulfur wrote:
    ... Inna sprawa, że nie wiedziałem, że ktoś "bawi się" w takie komunikaty na AVRach.
    No to wbrew pozorom nie jest jakieś straszne zagadnienie, a jeśli chociaż raz ktoś pokusi się aby to wykonać to dopiero wtedy doceni możliwości takiej obsługi w wielu różnych sytuacjach.
  • #76
    janbernat
    Level 38  
    Fakt, nie było pytania.
    Tylko sugestia- czy można to zrobić inaczej?
    Bo w zasadzie robię to dotychczas tak jak to opisał mirekk.
    Z całej dyskusji dotarło do mnie tylko to że można spróbować zastosować bufor- czy pierścieniowy czy FIFO to tak dokładnie nie zrozumiałem.
    Jakąś implementację bufora w C ściągnę z netu i będę próbował zrozumieć a potem przerobić jakiś swój program aby działał na takim buforze.
    A tu podaję "skompilowany" kod.
    Jest to "kompilacja" w sensie literackim.
    Komu się chce niech przeczyta komentarze- może o kimś zapomniałem.
    Mój "wkład" oceniam na jakieś 10%.
    
    #include <stdint.h>
    #include <avr/io.h>
    #include <stdlib.h>
    #include <avr/interrupt.h>
    #include "HD44780.h"	//to jest radzia
    #include <avr/sfr_defs.h>
    #include "TWI.h"		//i to też jest radzia
    
    #define LICZBA_KANALOW      4 //cztery kanały- tu można zwiększyć ilość kanałów 
    #define NAPIECIE_AKU_MIN	11.0
    #define NAPIECIE_AKU_MAX	14.0
    #define NAPIECIE_SOLAR_MIN  13.0
    #define START_LADOWANIE		PORTB&=~(1 << PORTB4); // clear PORTB0      
    #define STOP_LADOWANIE		PORTB|=(1<<PORTB4);	// set PORTB0
    #define MASKA_ODCZYT		0b11110000
    #define MASKA_WYJSCIE		0b11110000
    #define PCFADDR 0x20
    #define maska 0x01
    
    volatile uint8_t flaga_pomiaru;
    volatile uint8_t flaga_przycisku;
    volatile uint8_t stan_przyciskow;
    unsigned int napiecie[LICZBA_KANALOW];
    unsigned int debounce(void);
    void SetAdcKanal(void);	// prototyp
    
    int main(void)	// tu jest początek main() i tu ustawiamy konfigurację rejestrów
    {   
    	char buffer_nap[LICZBA_KANALOW];
    	char *text="Kolejne";
        char *text1="testy";
    	uint8_t i;	
    	uint8_t external_supply=0;
    	uint8_t stany;
    	int j, acc;
    	char tab[9]="        ";
    
        sei();
        DDRB=255;
        PORTB=255;
    	DDRC=0;
    	PORTC=0xf0; 
     	TCCR0=_BV(CS00)|_BV(CS02);// TIMER0 przepełnia się co 16ms
        ADMUX = _BV(REFS0)|_BV(REFS1) ;//Wewnętrzne źródło napięcia odniesienia-ok.2.56V    |_BV(ADLAR)   
        ADCSRA = _BV(ADATE)|_BV(ADEN)|_BV(ADIE)|_BV(ADPS0)|_BV(ADPS1)|_BV(ADPS2)|_BV(ADSC);//|_BV(ADATE)   
    	OCR0=127;					//ustawienie rejestru porównania w Timer0 na połowę zakresu
    	SFIOR=_BV(ADTS0)|_BV(ADTS1); // ustawienie startu przetwarzania ADC po porównaniu z OCR0 Timer0
    	TIMSK=_BV(TOIE0);
    
    	TWSR = 0;                         //brak preskalera
    	TWBR=72;
    
        LCD_Initalize();
        LCD_WriteText(text);
        LCD_GoTo(0,1);
        LCD_WriteText(text1);      
        _delay_ms(300);
        LCD_GoTo(0,0);
        LCD_WriteText("                ");// to jest szybciej niż LCD_Clear
    	LCD_GoTo(0,1);
    	LCD_WriteText("                ");
    	LCD_GoTo(5,0);											//dopasować do swoich potrzeb
    	LCD_WriteText("V");
    	LCD_GoTo(5,1);
    	LCD_WriteText("V");
    	LCD_GoTo(12,0);
    	LCD_WriteText("A aku");
    	LCD_GoTo(12,1);
    	LCD_WriteText("A solar");
    
    	while(1)			//A tu zaczyna się nieskończona pętla
    	{
    		
    		if(flaga_pomiaru)		//Tu sprawdzamy flagę i dlatego ten
    						//fragment wykonuje się co 1/4s- bo co 1/4s jest wyzwalany ADC- porównaniu z OCR0 Timer0
    		{			//Przez resztę czasu śpi.LCD też pokazuje co 1/4s.
    			for(i = 0; i < LICZBA_KANALOW; i++)		//pętla pomysłu gaskoina
    			{
    			    if(i<2)
    				{
    			        LCD_GoTo(0,i);
    			        LCD_WriteText("     ");
    			        LCD_GoTo(0,i);
    		        } 
    				else
    				{
    				    LCD_GoTo(7,i-2);
    				    LCD_WriteText("     ");
    			        LCD_GoTo(7,i-2);
    			    }
    			    LCD_WriteText(dtostrf(napiecie[i]/50.0,5,2,buffer_nap));
    
    				if(external_supply)
    				{
    					LCD_GoTo(20,0);
    					LCD_WriteText("External supply");										
    				}
    				else
    				{
    					LCD_GoTo(20,0);
    					LCD_WriteText("Solar is on    ");
    				}
    
    				
    			}
    	    flaga_pomiaru=0;	// A tu flagę kasujemy- po 1/4s znowu się ustawi
    		}		//Tu się kończy ten fragment co się wykonuje co 1/4s
    
    		if(flaga_przycisku>=4)	//Ten fragment wykonuje się co16ms x4 z Timer0
    		{						// to też zrozumiałem dzięki pomocy sulfura
    			static uint8_t toggle=1;
    			uint8_t	stan_przyciskow;
    			stan_przyciskow=debounce();
    			
    			if(stan_przyciskow==0x10)
    			{
    				if(toggle)
    				{
    				PORTB=(0x1|MASKA_WYJSCIE);//wyjście tylko na port0-3
    				toggle=0;
    				}
    				else
    				{
    				PORTB=(0xC|MASKA_WYJSCIE);
    				toggle=1;
    				}
    
    				twistart();
    				twiwrite(0x40 | (PCFADDR << 1));
    				twiwrite(0xff);
    				twistop();
    
    				twistart();
    				twiwrite(0x41 | (PCFADDR << 1));
    				stany = twiread(NOACK);
    				twistop();
    
    				LCD_GoTo(20,1);
    
    				int i = 128;					//pomysł zastąpienia itoa() autorstwa sulfura
    				for (; i; i>>=1)
    				LCD_WriteData((stany&i?'1':'0'));
    			}
    
    			if(stan_przyciskow==0x20)
    			{
    				if(toggle)
    				{
    				PORTB=(0x2|MASKA_WYJSCIE);
    				toggle=0;
    				}
    				else
    				{
    				PORTB=(0xC|MASKA_WYJSCIE);
    				toggle=1;
    				}
    
    				twistart();
    				twiwrite(0x40 | (PCFADDR << 1));
    				twiwrite(0xff);
    				twistop();
    
    				twistart();
    				twiwrite(0x41 | (PCFADDR << 1));
    				stany = twiread(NOACK);
    				twistop();
    
    				LCD_GoTo(20,1);
    
    				for(j=0; j<8; j++)			// a to pomysł tymona_x- też pięknie działa 
    				{
    			    acc=7-j;
    			    tab[j] = ((stany >> acc) & maska) + 0x30;
    				}
    				LCD_WriteText(tab);
    											// nie sprawdziłem jeszcze pomysłu michałko- ale wkrótce sprawdzę
    			}
    		flaga_przycisku=0;
    		}	
    
    	// a tu można wsadzić resztę programu która się bedzie wykonywała z pełną prędkością 
    			if(((napiecie[0]/50.0)<NAPIECIE_AKU_MIN)&&((napiecie[1]/50.0)<NAPIECIE_SOLAR_MIN))
    			{
    				START_LADOWANIE	//ładowanie włączamy stanem niskim
    				external_supply=1;
    			}
    			if(((napiecie[1]/50.0)>=NAPIECIE_SOLAR_MIN)||((napiecie[0]/50.0)>=NAPIECIE_AKU_MAX))
    			{
    				STOP_LADOWANIE	//a wyłączamy stanem wysokim
    				external_supply=0;
    			}
    	}
    }
    
    ISR(ADC_vect)//a tu przerwanie od ADC- ADC jest wyzwalane przez OCR TIMER0 co 8ms  
    {            //ale pomiar wyświetlany jest co 256ms
    	static uint8_t liczniczek_pomiaru;//TIMER0 nie starcza na 1/4s- to niech liczniczek to zlicza 
    	liczniczek_pomiaru++;
    	if(liczniczek_pomiaru>=32)//32x8ms- 256ms
    		{
    		liczniczek_pomiaru=0;
    		SetAdcKanal();//ta funkcja zmienia kanal i zapisuje w tablicę napiecie[kanal]
    		flaga_pomiaru=1;	//flaga pomiaru ustawia się co 256ms
    		}
    	TIFR |= (1 << OCF0); // a tu nie było przerwania od OCR0 Timer0- ale skasować flagę trzeba- no to kasujemy 
    }						// w ten sposób wykorzystujemy to przerwanie też jako timer
    						// sposób trochę pokręcony- sposób gaskoina jest prostszy do zrozumienia
    void SetAdcKanal(void)// a tu funkcja zmieniająca kanały- definicja	- to chyba też gaskoina
    {
    	static uint8_t kanal;
    	ADMUX = ((ADMUX&0xE0)+ kanal);//zmienić kanał	
    	napiecie[kanal++]=ADC;			// zapisać pomiar w tablicę
        if(kanal>=LICZBA_KANALOW)		
    	kanal=0;  
    } 
    
    ISR(TIMER0_OVF_vect)		// no ale przerwanie od TIMER0 też wykorzystamy- do debounce
    {
    	flaga_przycisku++;
    }
    
    unsigned int debounce(void)// funkcja Freddiego Chopina
    {
    	static unsigned int last;
    	unsigned int port;
    	unsigned int wynik;
       
    	port = PINC&MASKA_ODCZYT; //odczyt tylko z poza maski
        wynik=port&(~last);
        last=port;
       
       return wynik;
    } 
    
  • #77
    trol.six
    Level 31  
    janbernat wrote:
    A program który "udaje" wielowątkowość
    /.../
    Oczywiście inne przerwania też można/należy wykorzystać.

    Ale jaka jest różnica pomiędzy programem który wykonuje się tak:
    ABCABCABC

    a tak:
    AAABBBCCC

    Otóż procesor ma obsłużyć kilka rzeczy i musi mieć na to czas. Jak nie ma czasu, to wielowątkowość czy pseudowielowątkowość mu nic nie pomoże.

    Przerwania oczywiście są bardzo dobre, trzeba tylko pamiętać, ile czasu maksymalnie mamy do odebrania przerwania. Ponieważ jest to czas nieprzekraczalny bez utraty informacji.

    Człowiek obsługujący menu może poczekać, transmisja 100 znaków przez RS nie bardzo. A jeśli bufor RS nam się przepełni, to raczej wygląda na to że mamy za słaby procesor do obsługi zadań. Albo w programie marnujemy cenne cykle.

    Jeśli z proca wyciskamy maks, to zwykle nie ma jednego uniwersalnego zastosowania. Są też inne rzeczy które nie mogą czekać. I to bardziej niż RS, np. zczytywanie sekwencji pomiarów z ADC przy określonej częstotliwości próbkowania.
  • #78
    nsvinc
    Level 35  
    tmf wrote:
    [...]mamy więc obiekty do których trafiają. A że da się to zrobić w C - da się, tylko po co?

    Heh...A w C nie da się wprowadzić "obiektów"? Przecież wystarczy używać namiętnie typedef struct - tyle że zamiast wpychać funkcje/ich prototypy wewnątrz takiego structa, wpychamy wskazniki na funkcje, ktore przyjmuja voidowy wskaźnik na typ (struct) który funkcję wywołał + deskryptor (gdy jest taka konieczność, ale często nie ma...).
    Wtedy struktury udające obiekty odpalają funkcje udające metody. Nie ma rowniez zadnej komplikacji by odpalić "metode" dowolnej struktury - wystarczy wywołać odpowiednią funkcję podając jej jako parametr wskaźnik na strukturę, której "metodę" chcemy użyć.

    BTW, C++ na AVR to IMHO przerost formy nad treścią...:]

    trol.six wrote:
    Ale jaka jest różnica pomiędzy programem który wykonuje się tak:
    ABCABCABC

    a tak:
    AAABBBCCC

    Istotna!. Prosty przykład: im częsciej umożliwiasz procesorowi "przełączenie" zadania, tym mniejszych buforów potrzeba na przychodzące dane. Ogólnie nie ma przecież problemu, żeby np. do UARTa użyc 1048576kB bufora (hipotetycznie), i raz na 30 sekund przez 10 sekund zużywać 100% czasu procesora na przetworzenie danych z tego bufora. Ale po co?. Również przegięcie w drugą stronę jest złe - gdy procesor co 5 instrukcji wątka zużyje 100 instrukcji na scheduler lub nawet tylko context switch, mamy już czyste marnowanie czasu.

    Przykład już był - np. 4portowy crosslink, gdzie jeden z portów jest USB. A USB nie daruje poślizgu - nie zdążysz od razu, to czekasz grzecznie aż host po raz kolejny sobie o tobie przypomni. Efekt: zapychający się w urządzonku bufor + przywiecha w transmisji danych do PC - bo interrupt jest mały, izochroniczny jest bez kontroli danych - pozostaje bulk, ale trzeba zdążyć...
  • #79
    tmf
    Moderator of Microcontroller designs
    nsvinc wrote:
    tmf wrote:
    [...]mamy więc obiekty do których trafiają. A że da się to zrobić w C - da się, tylko po co?

    Heh...A w C nie da się wprowadzić "obiektów"? Przecież wystarczy używać namiętnie typedef struct - tyle że zamiast wpychać funkcje/ich prototypy wewnątrz takiego structa, wpychamy wskazniki na funkcje, ktore przyjmuja voidowy wskaźnik na typ (struct) który funkcję wywołał + deskryptor (gdy jest taka konieczność, ale często nie ma...).
    Wtedy struktury udające obiekty odpalają funkcje udające metody. Nie ma rowniez zadnej komplikacji by odpalić "metode" dowolnej struktury - wystarczy wywołać odpowiednią funkcję podając jej jako parametr wskaźnik na strukturę, której "metodę" chcemy użyć.

    BTW, C++ na AVR to IMHO przerost formy nad treścią...:]


    No przecież pisałem, że można i w c. A że jak sam napisałeś zamiast pisać program piszesz coś co w C++ masz od reki to inna sprawa. Ale jak ktoś lubi... Dodaj jeszcze receptę jak zaimplementować metody wirtualne - też się da. Co do przerostu - możesz wskazać konkretnie gdzie w przypadku C++ na AVR ten przerost występuje?
  • #80
    trol.six
    Level 31  
    nsvinc wrote:
    trol.six wrote:
    Ale jaka jest różnica pomiędzy programem który wykonuje się tak:
    ABCABCABC

    a tak:
    AAABBBCCC

    Istotna!. Prosty przykład: im częściej umożliwiasz procesorowi "przełączenie" zadania, tym mniejszych buforów potrzeba na przychodzące dane. Ogólnie nie ma przecież problemu, żeby np. do UARTa użyc 1048576kB bufora (hipotetycznie), i raz na 30 sekund przez 10 sekund zużywać 100% czasu procesora na przetworzenie danych z tego bufora. Ale po co?.

    Zgodze się. Porcjowanie danych ma sens. Tylko że w moim mniemaniu, jest to zwykła rzecz jaką się realizuje w mikrokontrolerach.
    Stąd pod pojęciem AAA rozumiałem jakiś proces jednostkowy.
    Teraz byśmy pisali np: A1C1A2B1A3B2A4C1
    np:
    B1 wciśnięcie przycisku
    B2 zmiana menu na wyświetlaczu
    A1-4 kolejne odczyty RS'a
    C1 - inne

    To co mi czasem brakuje w avrach to brak priorytetu przerwania, które były w 51. Nie wiem jak to jest np w armach. Obsługa programowa czasem jest niaładna w takich sytuacjach. Szczególnie właśnie wtedy kiedy procesor realizuje wiele różnych zadań.

    nsvinc wrote:
    Przykład już był - np. 4portowy crosslink, gdzie jeden z portów jest USB. A USB nie daruje poślizgu - nie zdążysz od razu, to czekasz grzecznie aż host po raz kolejny sobie o tobie przypomni. Efekt: zapychający się w urządzonku bufor + przywiecha w transmisji danych do PC - bo interrupt jest mały, izochroniczny jest bez kontroli danych - pozostaje bulk, ale trzeba zdążyć...

    Dlatego napisałem o maksymalnych czasach :) (choć tylko w kontekście przerwań).
  • #81
    nsvinc
    Level 35  
    tmf wrote:
    Dodaj jeszcze receptę jak zaimplementować metody wirtualne[...]

    Recepta:
    W wczesniej opisanej przykładowej strukturze udającej obiekt można zadeklarować wiele wiszących wskaźników - na inne typy (struct) lub funkcje. Gdy teraz postawimy jakiś wskaznik na funkcję z struktury A na funkcję która była dedykowana strukturze B, to mamy metody wirtualne... Wskazniki takie mogą również wisieć - ale spójny kod wymaga, by były one zainicjalizowane zerami w "konstruktorze" strukturki. Konstruktor to nic innego jak "metoda" pod wskaźnikiem, co opisywałem w poprzednim poście. Przy czym wtedy taki konstruktor musi istnieć (nie ma metody implementacji domyślnego konstruktora)

    Dziedziczenie:
    Void* wewnątrz struktury A. Pod ten wskaznik podczepiamy albo inna, pojedynczą strukturę B (więc struktura A "dziedziczy" wszystko to, co ma struktura B ) albo wskaźnik na tablicę wskaźników - wtedy struktura A może "dziedziczyć" zmienne wielu innych struktur.

    Hm...O wprowadzaniu "obiektów" w C możnaby raczej założyć topic :]

    tmf wrote:
    Co do przerostu - możesz wskazać konkretnie gdzie w przypadku C++ na AVR ten przerost występuje?

    Może się mylę, ale zdaje sie że kompilatory C++ tworzą cięższy i mniej wydajny kod niż kompilatory ANSI C. AVRek ma tak mało mocy, że np. 50 dodatkowych instrukcji asma odbija się już na wydajności...
    Ogólnie nie ma przecież nic złego z C++ na AVR, ale to jest bardzo rzadko spotykane (pierwsze słyszę od ciebie, że się w ogóle da :P i ktoś zadał sobie trud żeby napisać kompilator...)

    trol.six wrote:
    To co mi czasem brakuje w avrach to brak priorytetu przerwania, które były w 51. Nie wiem jak to jest np w armach.

    No zależy jakich. ARMy w najpopularniejszej wersji 7TDMI,9EJ(-S) żadko mają jakikolwiek sensowny kontroler przerwań - przerwania są dwa, a reszta tylko softem...Niektórzy producenci dodają do tego VIC, ale często nie obsługuje on konfigurowalnego priorytetowania, a ogolnie z przerwaniami jest gorzej niż na AVR :/
    CM3 ma bliski rdzeniowi NVIC z przedziwną acz przydatną kontrolą priorytetów, a inne Cortexy tego już nie mają, i wtedy zależy to od producenta, jaki VIC sobie wsadzi w krzem i czy wogole wsadzi...
  • #82
    Freddie Chopin
    MCUs specialist
    nsvinc wrote:
    tmf wrote:
    Dodaj jeszcze receptę jak zaimplementować metody wirtualne[...]

    Recepta:
    [...]
    Hm...O wprowadzaniu "obiektów" w C możnaby raczej założyć topic :]

    I te wszystkie kombinacje tylko po to, żeby otrzymać to co w C++ jest "gratis"... Bez sensu...

    Quote:
    Może się mylę, ale zdaje sie że kompilatory C++ tworzą cięższy i mniej wydajny kod niż kompilatory ANSI C.

    Wydaje Ci się - kod maszynowy będzie identyczny w 99% przypadków.

    4\/3!!
  • #83
    wjeszak
    Level 11  
    Przeczytałem cały wątek (miejscami faktycznie dobrze zakręcony) :). Załóżmy taką sytuację:
    
    while(1)
    {
       if(flaga_1) <- flaga ustawiana przez przerwanie
       {
          zrob_cos();
          flaga_1 = 0;
       }
       if(flaga_2)
       {
          jakas_funkcja(); <- ta funkcja działa długo, powiedzmy ok. 1 minuty
          flaga_2 = 0;
       }
    }
    


    Tak więc przez cały czas trwania jakas_funkcja() procesor nie będzie reagował na flaga_1. Wsadzenie funkcji zrob_cos() do obsługi przerwania jest niemożliwe z uwagi na fakt, że to blokowałoby na zbyt długi czas pozostałe przerwania.

    Jak sprawę rozwiązać ?
  • #84
    mirekk36
    Level 42  
    wjeszak wrote:

    Jak sprawę rozwiązać ?


    W bardzo prosty sposób - napisać wszystkie funkcje jako nieblokujące. Co to za przykład gdzie wrzucasz jakąś funkcję, która trwa około minuty i blokuje inne. Zmieniasz od razu program na zwykły liniowy a nie - "pseudo-wielowątkowy".

    Twoje bardziej szczegółowe pytanie czy proces poszukiwania powinien się skupić na tym jak pisać funkcje żeby działały w sposób nieblokujący, niż szukać metody jak do tego całego "ambarasu" znowu wstawić jakiegoś ślimaka typu jakas_funkcja().
  • #85
    wjeszak
    Level 11  
    Faktycznie trochę z grubej rury pojechałem :)
    Naświetlę temat bardziej szczegółowo.
    A więc:
    Co sekundę jest wywoływane przerwanie INT0 z PCFa. W obsłudze tegoż przerwania wysyłam na UART0 ramkę danych do czujnika. Następnie czekam na odpowiedź (w przerwaniu). Jeśli otrzymana ramka jest OK (parsuję w przerwaniu USART0) to dorzucam do niej czas z PCFa i zapisuję na karcie SD. I tak się to powtarza i działa elegancko.

    Schody zaczynają się gdy chcę przesłać zapisane dane przez moduł GSM. Robię tak: jeśli pobrana z PCFa godzina równa się zadanej przez użytkownika to kończę zapis w aktualnym pliku, zakładam nowy i uruchamiam funkcję (niech to będzie właśnie ta długo wykonująca się jakas_funckja()) aby połączyć się i wysłać dane z poprzedniego pliku. Z modułem się komunikuję poprzez USART1 (znaki od modułu odbieram w przerwaniu).
    I teraz to z czym się męczę:
    Jeśli funkcję zapisu na karcie umieszczę w przerwaniu USART0 to przy łączeniu się modułu raz na jakiś czas (losowo) gubię dane z modułu. Sądzę, że jest to związane z długim czasem wykonywania się przerwania USART0. Jeśli natomiast umieszczę funkcję zapisu w pętli głównej (uruchamianą flagą z przerwania USART0 -> przez co skracam obsługę tegoż przerwania), to wspomniana "ślimacząca" :) się funkcja blokuje wykonanie się funkcji zapisu.Nie mogę pozwolić sobie na zatrzymanie zapisywania na czas połączenia i transferu danych.

    mirekk - czy mógłbyś naświetlić sprawę funkcji nieblokujących ?
    Być może problem jest prosty, tylko ja jestem już zapętlony niczym
    while(1) {};
    :)
  • #86
    piti___
    Level 23  
    Napisz funkcję obsługi modemu która będzie działać krokowo:

    
    void func(void)
    {
    	static krok = 1;
    
    	switch(krok)
    	{
    		case 1: 
    		{
    			krok = 2;
    			break;
    		}
    
    		case 2: 
    		{
    			krok = 3;
    			break;
    		}
    		
    		case 3: 
    		{
    			krok = 1;
    			break;
    		}
    	}
    }
    



    Między krokami możesz obsługiwać inne procesy.
  • #88
    mirekk36
    Level 42  
    wjeszak --> tak jak padły już tutaj dwie odpowiedzi, funkcje nieblokujące można pisać także na milion różnych sposobów i nie ma wcalej jednego uniwersalnego bo wszystko zależy od aktualnych potrzeb, dlatego też nie da się tego wszystkiego opisać jedną krótką regułką. No ale jak podajesz konkretny przykład to sam widzisz, że już łatwiej podpowiedzieć. Tyle że moim zdaniem w takim przypadku też najłatwiej jest zastosować (to wręcz podstawa) buforowanie odczytu i zapisu, które działa także na przerwaniach.

    Przecież wtedy tylko wrzucasz do bufora to co akurat jest w danym momencie do zapisania - i to będzie trwało bardzo szybko po czym wracasz do pętli głównej (nie blokujesz) a w tym czasie , w tle - dane z bufora będą już obsługiwane przez przerwanie znak po znaku.
  • #89
    wjeszak
    Level 11  
    Dziękuję bardzo za podpowiedzi.
    Buforowanie mam oczywiście zrobione w przerwaniu.
    Chodziło mi jedynie o coś takiego, co podsunął piti___. Myślę, że to sprawę rozwiąże całkowicie i skutecznie.
  • #90
    mirekk36
    Level 42  
    wjeszak wrote:
    Dziękuję bardzo za podpowiedzi.
    Buforowanie mam oczywiście zrobione w przerwaniu.
    Chodziło mi jedynie o coś takiego, co podsunął piti___. Myślę, że to sprawę rozwiąże całkowicie i skutecznie.


    No i o to chodzi, bo już podział na kroki czyli same zdarzenia gdy "coś" pojawiło się do zapisu do bufora - to naturalny tok dalszego postępowania w takich programach.