Elektroda.pl
Elektroda.pl
X

Search our partners

Find the latest content on electronic components. Datasheets.com
Elektroda.pl
Please add exception to AdBlock for elektroda.pl.
If you watch the ads, you support portal and users.

[ATMEGA8][C] dziwny problem z tablicą

galsan 15 Nov 2008 10:21 1998 17
  • #1
    galsan
    Level 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ę:

    Code:

    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:


    Code:

       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
    Dr.Vee
    VIP Meritorious for electroda.pl
    Ja np. myślę, że brakuje Ci podstaw języka C :)

    Deklaracja:
    Code:

    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
    MichalKl
    Level 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
    BoskiDialer
    Level 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
    MichalKl
    Level 16  
    Quote:

    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
  • Helpful post
    #6
    BoskiDialer
    Level 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
    galsan
    Level 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
    MichalKl
    Level 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
    galsan
    Level 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...
  • #11
    galsan
    Level 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
    MichalKl
    Level 16  
    Code:


       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
    MichalKl
    Level 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ś:
    Quote:

    albo odwrotnie, albo inaczej, albo jeszcze jakos. sposobow sa setki. mozna pominac dzielenie w ogole i uzyc odejmowania, co w zasadzie jest szybsze.
  • #15
    Dr.Vee
    VIP Meritorious for electroda.pl
    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
    Code:

    d = div(d.quot, 10);
    bardziej mi się podoba ;)

    Pozdrawiam,
    Dr.Vee
  • #16
    galsan
    Level 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:
    Code:

    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:
    Code:

       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:
    Code:
    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
    Freddie Chopin
    MCUs specialist
    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.

    Code:

    /*
    +———————————————————————————————————————————————————————————————————————————————+
    | 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:

    Code:

    #define STR_DIGIT_MAX      5


    4\/3!!
  • #18
    galsan
    Level 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