logo elektroda
logo elektroda
X
logo elektroda
Adblock/uBlockOrigin/AdGuard mogą powodować znikanie niektórych postów z powodu nowej reguły.

[ATMEGA8][C] dziwny problem z tablicą

galsan 15 Lis 2008 10:21 2043 17
  • #1 5740650
    galsan
    Poziom 12  
    Witam, zacznę od razu od kodu. Otóż pisałem taką funkcję do której przekazywane są kilkucyfrowe liczby a funkcja rozbija je na osobne cyfry a wynik umieszcza w tablicy eq do której adres jest zwracany przez funkcję:

    
    volatile uint8_t* conv_tab(int count)
    {
    	int x;			// dzielnik
    	uint8_t j, i = 0;	// zmienna pomocnicza
    	volatile uint8_t *eq = 0;
    	div_t dv;
    
    	for(x = 1; count > x; x *= 10, i++); // wyznaczenie przedziału dziesiętnego liczby
    	x /= 10;
    
    	if( count < 10)			// jeśli liczba jest już mniejsza od 10 - zwróć ją
    	{ 
    		eq[1] = count;
    		return eq;
    	}
    
    	for(j = 0; j < i; j++)
    	{
    		dv = div(count, x);		// podziel cout / x
    		eq[1+j] = dv.quot;		// wynik dzielenia wpisz do tablicy eq
    		count = dv.rem;			// przygotowanie reszty do kolejnego dzielenia
    		x /= 10;				// zmniejsz dzielnik 10 - krotnie
    	}
    
    	return eq;
    }
    


    Wszystko działa tylko z zapisem do tablicy eq jest jakoś dziwnie. Mianowicie nie mogę nic zapisać pod pierwszą komórkę tablicy eq[0].
    Gdy odczytuję tą tablicę zawsze na eq[0] jest cyfra 0. Nawet jak napiszę eq[0] = 3. Dlatego też wszelki zapis (a potem odczyt) muszę wykonwywać od eq[1].

    Następnie, przekazaną tablicę (adres do niej) odczytuję tak:


    
    	char j;
    	volatile uint8_t *t;
    	t = conv_tab(123);
    
    	for(j = 0; j < 3; j++)
    		send(t[j+1] + 48, 0);
    


    I tutaj tablicę t również muszę odczytywać od t[1].. w przeciwnym wypadku dostaję śmieci..

    Co o tym myślicie?

    Pozdrawiam, Galsan
  • #2 5740704
    Dr.Vee
    VIP Zasłużony dla elektroda
    Ja np. myślę, że brakuje Ci podstaw języka C :)

    Deklaracja:
    
    volatile uint8_t *eq = 0;
    deklaruje wskaźnik, któremu przypisujesz adres zero. Jeśli odwołasz się do eq[0], to odwołujesz się komórki pamięci pod adresem 0.

    Druga rzecz - zwracasz z funkcji wskaźnik - nawet gdybyś tą tablicę zadeklarował poprawnie, to w jaki sposób zapewnisz, że nie zostanie ona usunięta? Przecież zmienne lokalne są przydzielane na stosie, a więc nie mogą "przeżyć" powrotu z funkcji...

    Cokolwiek działa Ci tylko dlatego, że w AVR nie ma ochrony pamięci :) Skompiluj ten kod na PC, to od razu dostaniesz wyjątek nieprawidłowego dostępu do pamięci.

    Żeby poprawnie napisać taki kod masz 3 wyjścia:
    1) zadeklarować globalną tablicę,
    2) zadeklarować lokalną tablicę w funkcji wywołującej i przekazywać jej adres do funkcji wywoływanej,
    3) przydzielać pamięć dynamicznie przez malloc() i zwracać przez free().

    Pozdrawiam,
    Dr.Vee
  • #3 5740734
    MichalKl
    Poziom 16  
    Witam

    Dopiero wstałem, więc może tego nie widzę... ale gdzie tablica jest zdeklarowana? Wskaźnik jest, tablicy nie widzę.

    eq[1+j] = dv.quot; - już w pierwszym przebiegu pętli (zerowym) nie odnosisz się do zerowego elementu tablicy.

    Niestety nie znam struktury div_t dv :/, więc nie wiem co tam robisz.

    Za pomocą dzielenia modulo zrobiłbyś to szybciej i bez gmatwania kodu.
  • #4 5740746
    BoskiDialer
    Poziom 34  
    Wskaźnik zawierający adres 0 - bez wątpienia pojawią się dziwne rzeczy: w przestrzeni pamięci pod adresami 0-31 dostępne są rejestry procesora, więc komórka eq[0] będzie odpowiadać rejestrowi r0 (rejestr tymczasowy dla wielu operacji) - ta "komórka" będzie się zmieniać, eq[1] to będzie r1 (rejestr, który musi zawierać wartość zero [konwencja kompilatora - niektóre operacje można zoptymalizować jeśli posiada się zero w którymś z rejestrów]) - zmiana tej komórki spowoduje wysypywanie się reszty kodu, od r2 do r17 są rejestry prywatne ([konwencja] muszą być odkładane na stos jeśli są używane przez funkcję - funkcje wywołujące nie zmieniają ich, gdyż też muszą je odkładać jeśli korzystają), zmiana ich spowoduje wysypywanie się funkcji nadrzędnych, które wywołują aktualną funkcję (jeśli korzystają z tych rejestrów); itd...

    Najlepszy pomysł rozwiązujący wszystkie problemy, to przekazywać do funkcji wskaźnik na tablicę, przez co tablica może zostać utworzona na stosie przez funkcję nadrzędną i wypełniona przez aktualną funkcję. Nie trzeba żadnej globalnej tablicy, nie trzeba funkcji zarządzania stertą - nawet jakby, to wskaźnik do przydzielonego bloku może zostać przekazany przez funkcję nadrzędną - dla funkcji nie powinno to robić różnicy, czy blok jest na stosie czy stercie.
  • #5 5740801
    MichalKl
    Poziom 16  
    Cytat:

    Najlepszy pomysł rozwiązujący wszystkie problemy, to przekazywać do funkcji wskaźnik na tablicę


    Ee tam.Po co w tym przypadku komplikować sobie życie. W zasadzie rozwiązań jest dużo.
    Tablice można utworzyć w funkcji, tylko dodać magiczne słowo static :)

    Druga sprawa to taka, że galsan chce liczbę rozbić na poszczególne cyfry.
    A tu wystarczy zwykłe dzielenie modulo w pętli, bez kombinowania.

    Pozdrawiam
  • Pomocny post
    #6 5740819
    BoskiDialer
    Poziom 34  
    Magiczne słowo static daje dokładnie to samo co zmienna globalna. Przekazywanie wyniku przez zmienne globalne owszem jest w niektórych przypadkach dobre lub jedyne możliwe (z przerwania do kodu), ale pomiędzy funkcjami - nigdy. Przyjmijmy, że mamy n funkcji, każda może zwrócić tablicę o rozmiarze m. Jeśli każda funkcja przydzieli sobie tą tablicę mającą m komórek zużycie pamięci będzie... duże... nieuzasadnione (cenne bajty które mogły by być wykorzystane na coś innego leżą jako bufor pod funkcję i nie mogą być wykorzystane ponownie - chyba, że kilka funkcji korzysta z jednej takiej tablicy). Dodatkowo takie rozwiązanie nie idzie w parze z wielowątkowością (jeśli ktoś ma zaimplementowaną; lub korzysta z dwóch "wątków" - główny oraz przerwania)
  • #7 5740908
    galsan
    Poziom 12  
    Wybrałem sposób przekazania wskaźnika do funkcji. Wszystko działa jak należy. Też uważam, że to najlepszy sposób, chocby z punktu widzenia mojej funkcji, gdyż w programie głównym zdefiniuję tablicę już o konretnej liczbie elementów (tyle ile cyfr chce uzyskać) i taką konkretną tablicę prześlę do funkcji, tzn. wskaźnik.

    A dzielenie modulo w pętli rozważę... w sumie nie pomyślałem o tym wcześniej..

    Dziękuję za sugestie
  • #8 5740925
    MichalKl
    Poziom 16  
    Oczywiście, masz rację.

    Jak będzie globalna ewentualnie zdefiniowana przez funkcję nadżędną - to tak jak mówiłeś, do funkcji przez wskaźnik. Rozwiązanie dosyć eleganckie, można taką tablicę wykorzystać w roli np.: bufora także do innych funkcji.

    Jak będzie statyczna - (oczywiście również masz rację) tu akurat chodzi mi o galsan, po prostu kod jest bardziej przyjazny, wszystko widać jak na dłoni, zgodny z jego zarysem programu.

    Poza tym bardzo nie lubię wszystkiego, co jest zdeklarowane globalnie (nie wiem, pewnie zboczenie z C++).

    Idealnym rozwiązaniem byłaby dynamiczna alokacja pamięci, tylko nie wiem jak to działa z AVR-ami (podejrzewam duże straty w pamięci programu na taką imprezę).
  • #9 5740939
    galsan
    Poziom 12  
    Mój program nie potrzebuje raczej dynamicznej alokacji ponieważ będzie wykorzystywana tablica o określonej wielkości i w dodatku bardzo często więc nie ma sensu jej tworzenia i usuwania.

    Btw, Michalku mógłbyś mi zaprezentować jak za pomocą dzielenia modulo miałbym rozdzielić np 2345 na poszczególne cyfry? Bo jakoś mi to nie idzie...
  • #10 5740967
    Freddie Chopin
    Specjalista - Mikrokontrolery
    2345%10=5
    (2345/10)%10=4
    (2345/10/10)%10=3
    (2345/10/10/10)%10=2

    albo odwrotnie, albo inaczej, albo jeszcze jakos. sposobow sa setki. mozna pominac dzielenie w ogole i uzyc odejmowania, co w zasadzie jest szybsze.

    4\/3!!
  • #11 5740991
    galsan
    Poziom 12  
    no to w zasadzie moja funkcja robi bardzo podobnie, tylko wykorzystuje w niej wygodną strukturę div_t która po podzieleniu przechowuje już wynik dzielenia w zmiennej quot a reszte w zmiennej rem. To tak a propos tego że ktoś pisał że nie wie co to za struktura. (znajduje się w stdlib.h)
  • #12 5741059
    MichalKl
    Poziom 16  
    
    
       int buf[4]; 
       int ptr=2345;
       unsigned char l;
    
       for(l=4;l>0;l--)
    	{
    	buf[l-1]=(ptr%10);
    	ptr/=10;
    	}
    
    


    Możesz dopracować, przerobić sobie na funkcję, etc
  • #14 5741105
    MichalKl
    Poziom 16  
    Spokojnie!! Jak to mawiał mój nauczyciel od telekomunikacji: nie wzbudzaj się.

    Jak już powiedziałem, nie znam struktury div_t (już nadrobiłem zaległości).
    galsan pytał, więc udzieliłem odpowiedzi.

    Jak już powiedziałeś:
    Cytat:

    albo odwrotnie, albo inaczej, albo jeszcze jakos. sposobow sa setki. mozna pominac dzielenie w ogole i uzyc odejmowania, co w zasadzie jest szybsze.
  • #15 5741206
    Dr.Vee
    VIP Zasłużony dla elektroda
    W liczbie 16-bitowej mieści się 5 cyfr dziesiętnych, a nie 4. Poza tym funkcja używająca div() jest minimalnie krótsza (przynajmniej w avr-gcc 4.3.1). No i
    
    d = div(d.quot, 10);
    bardziej mi się podoba ;)

    Pozdrawiam,
    Dr.Vee
  • #16 5741424
    galsan
    Poziom 12  
    Uh ale się rozważań narobiło.. dzięki wszystkim, a mam jeszcze taki jeden warning od kompilatora.. Mój kod wygląda teraz tak:

    Funkcja dzieląca:
    
    uint8_t* conv_tab(int count, uint8_t* tab)
    {
    	long x;				// dzielnik
    	uint8_t j, i = 0;	// zmienna pomocnicza
    	div_t dv;
    
    	for(x = 1; count > x; x *= 10, i++); // wyznaczenie przedziału dziesiętnego liczby
    	x /= 10;
    
    	if( count < 10)				// jeśli liczba jest już mniejsza od 10 - zwróć ją
    	{ 
    		tab[0] = (uint8_t)count;
    		return tab;
    	}
    
    	for(j = 0; j < i; j++)
    	{
    		dv = div(count, x);		// podziel cout / x
    		tab[j] = dv.quot;		// wynik dzielenia wpisz do tablicy eq
    		count = dv.rem;			// przygotowanie reszty do kolejnego dzielenia
    		x /= 10;				// zmniejsz dzielnik 10 - krotnie
    	}
    
    	return tab;
    }
    


    Przekazywanie liczby, tablicy i wyświetlanie:
    
    	char j;
    	uint8_t t[4];
    	conv_tab(2133, t);
    
    	for(j = 0; j < 4; j++)
    		send(t[j] + 48, 0);     // <---- i tutaj dostaję warning
    


    Funkcja send() to moja funkcja wysyłająca znak do LCD, jej prototyp to:
    void send(uint8_t command, char flag)


    Treść warning`a:
    ../Cd_meter.c:94: warning: array subscript has type 'char'

    No i nie wiem o co mu biega.. przeciez chyba wszystkie typy mam zgodne co do czego przekazuję... (?)
  • #17 5741460
    Freddie Chopin
    Specjalista - Mikrokontrolery
    zmien typ zmiennej j na uint8_t.

    straszny smietnik w tym kodzie. w jednym miejscu zmienne uint8_t, potem jakies longi, przeplecione charem i intem.

    jak juz bylo mowione zmienna 16-bitowa (u ciebie dodatkowo jeszcze ze znakiem zapewne, bo int domyslnie jest signed) ma liczb 5, a nie 4. co wiecej zaraz wpadniesz na pomysl wysylania do LCD calego stringa i brakuje ci na koncu lancucha znakow terminujacego zera, ktore zajmowac powinno kolejna pozycje...

    optymalne toto tez nie jest [;

    jak chcesz to mozesz sobie przerobic te dwie funkcje, choc one sa dla innej architektury - odczyt stalych musisz zorganizowac po AVRowemu, czyli przez wbudowane funkcje do odczytu pamieci programu.

    
    /*
    +———————————————————————————————————————————————————————————————————————————————+
    | u8_t* strings_itoa_s16(s16_t value,u8_t *string_ptr)
    |
    | this function converts signed 'value' to an ASCII char array supplied in
    | 'string_ptr'. pointer to 'string_ptr' array returned.
    +———————————————————————————————————————————————————————————————————————————————+
    */
    
    u8_t* strings_itoa_s16(s16_t value,u8_t *string_ptr)
    {
    	u8_t *string_ptr_copy=string_ptr;
    	
    	if(value<0)
    	{
    		value=-value;
    		*string_ptr++='-';
    	}
    	
    	strings_itoa_u16(value,string_ptr);
    	
    	return string_ptr_copy;
    }	
    
    /*
    +———————————————————————————————————————————————————————————————————————————————+
    | u8_t* strings_itoa_u16(u16_t value,u8_t *string_ptr)
    |
    | this function converts unsigned 'value' to an ASCII char array supplied in
    | 'string_ptr'. pointer to 'string_ptr' array returned.
    +———————————————————————————————————————————————————————————————————————————————+
    */
    
    u8_t* strings_itoa_u16(u16_t value,u8_t *string_ptr)
    {
    	const u16_t strings_divisors[]={10000,1000,100,10};
    	u8_t *string_ptr_copy=string_ptr;
    	u16_t digit=0;
    
    	// skip some divisors for values with less than 5 digits (like _0_1234)
    	while(value<strings_divisors[digit] && digit<STR_DIGIT_MAX-1)
    		digit++;
    
    	while(value>9)							// convert to ASCII
    	{
    		*string_ptr++=value/strings_divisors[digit]+'0';
    		value%=strings_divisors[digit];
    		digit++;
    	}
        
        for(;digit<4;digit++)					// add trailing zeroes - if required
    		*string_ptr++='0';
        
    	*string_ptr++=(u8_t)value+'0';			// the final value after divisions
        
    	*string_ptr=0;							// terminating character
    
    	return string_ptr_copy;
    }    
    


    w naglowku:

    
    #define STR_DIGIT_MAX		5
    


    4\/3!!
  • #18 5741982
    galsan
    Poziom 12  
    do wysyłania stringa mam inną funkcję no i pamiętam o znaku końca ofkors.. ok zmieniłem faktycznie zmienne na unsigned. Wszystko już działa.

    Dzięki wszystkim.
    Pozdrawiam, galsan
REKLAMA