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

Optymalizacja WinAVR, gcc, uC. Gdzie popełniam błąd?

sepako 12 Lis 2008 12:54 2370 7
  • #1 5729579
    sepako
    Poziom 14  
    Witam
    Temat dotyczy optymalizacji kompilatora gcc dla uC AVR, dodam jeszcze że nie mam wielkiego doświadczenia w programowaniu w tym środowisku.
    A więc problem jest taki - przykładowy program poniżej:

    #define F_CPU 8000000
    #include <avr/io.h>
    #include <util/delay.h>
    int main(void)
    {
    DDRB = 0xff;
    PORTB = 0X00;
    uint8_t x=1;

    while(1)
    {
    PORTB = x;
    _delay_us(x++);
    }
    return(0);
    }

    generuje plik .HEX o wielkosci 4KB (2042 lini asm)
    Co wydaje mi sie wielkością olbrzymią jak na tak prostą funkcję.

    Tym bardziej że inny kompilator stworzył plik .HEX o wielkości 42 bajtów czyli średnio 100 razy mniejszy - programu robiącego dokładnie to samo.

    (Flaga optymalizacji w pliku Makefile jest ustawiona na -s )
    Program ze stałym argumentem funkcji _delay_us() tworzy .HEX o wielkości 111 bajtów.
    Pytanie wiec gdzie tkwi przyczyna tak dużego kodu w pierwszym przypadku - czy problem stanowi tu parametryzacja funkcji _delay_us()
    czy popełniłem gdzieś trywialny błąd?
  • #2 5729670
    marek-c
    Poziom 19  
    sepako napisał:
    Witam

    _delay_us(x++);
    }


    To jest ten problem.
  • #3 5729698
    BoskiDialer
    Poziom 34  
    Dokładniej chodzi o to, że _delay_us korzysta z liczb zmiennoprzecinkowych w celu wyliczenia liczby przebiegów pętli opóźniającej. Po podaniu stałej będzie to działać bez problemu (obliczenia zostaną wykonane przez kompilator), w przypadku zmiennych całość zostanie liczona w czasie działania programu.
  • #4 5729749
    sepako
    Poziom 14  
    No tak zgadza sie ze wartosc opoznienia wyliczana jest w programie bo tak ma byc i takie było założenie, ale zamiast:
    _delay_us(x++);

    moge zrobic:
    for (int i=0;i<x;i++) {
    _delay_us(1); }

    funkcjonalnie jest to samo i w drugim przypadku kompilator tworzy plik .HEX o "normalnej" wiekosci 0.112KB. Pytanie dlaczego kompilator w pierwszym przypadku tak bardzo rozbudowuje plik .HEX w jednym i drugim przypadku wykonuje x-razy funkcje _delay_us(1) - czy zasada jest nieparametryzowanie funkcji wbudowanych ?
  • #5 5729765
    Freddie Chopin
    Specjalista - Mikrokontrolery
    a skad pomysl ze jak napiszesz _delay_ms(x) to on wykona x-razy _delay_ms(1)? przeciez juz bylo pisane, ze wcale tak nie bedzie, a opoznienei zostanie wyliczone na podstawie liczb zmiennoprzecinkowych...

    4\/3!!
  • #6 5729794
    sepako
    Poziom 14  
    Nie wiem moze zle interpretuje funkcje _delay_us(x) - dla mnie to jest funkcja ktora czeka tyle us(mikro sekund) ile ma ma w argumencie funkcji
    _delay_us(10) czeka 10us .W nazwie funkcji jest us czyli jednostka podstawowa jest mikro sekunda a argument mowi ile razy powielic jednostke podstawowa no i zakladam ze argument jest zmienna typu calkowitego > 0.
  • Pomocny post
    #7 5729796
    BoskiDialer
    Poziom 34  
    W pierwszym przypadku w celu jak największej dokładności czasu opóźnienia jest wyliczana liczba przebiegów wewnętrznej pętli (która wykonuje się w 3 lub 4 cyklach, zależnie od tego, czy licznik może mieć 1 bajt, czy musi mieć 2 bajty) - te obliczenia są wykonywane na liczbach zmiennoprzecinkowych.

    Co do drugiego kodu, nie jest on funkcjonalnie taki sam - kontrola iteracji pętli for() powoduje, że opóźnienie będzie większe niż podane, dodatkowo przy zegarze 8MHz _delay_us(1) powinien zająć 8 cykli procesora, kontrola iteracji to w tym przypadku minimum 3 cykle (dec+brne), więc opóźnienie będzie równe 137% znamionowego. Warto zastanowić się nad innym sposobem uzyskania odpowiedniego opóźnienia lub nad napisaniem własnej funkcji opóźniającej, która będzie wycyzelowana i uwzględni to, że parametr jest zmienną - może to być pętla, w której jedna iteracja zajmuje dokładnie 8 cykli [zegar 8MHz] - da się to rozwiązać wstawką asemblera:
    static void _delay_us_var(unsigned int waitus) __attribute__((always_inline));
    static void _delay_us_var(unsigned int waitus)
    {
    	asm volatile(
    		"_loop_%=:"	"\n\t"
    		"nop"	"\n\t"
    		"nop"	"\n\t"
    		"nop"	"\n\t"
    		"nop"	"\n\t"
    		"sbiw %0, 1"	"\n\t"
    		"brne _loop_%="
    	::"w"(waitus)
    	);
    }
  • #8 5729827
    sepako
    Poziom 14  
    Zgadzam sie - czasy nie beda takie same w obu przypadkach ale mnie raczej chodzilo o zasade - w przypadku funkcji _delay_ms() (mili sekundy) wielkosc pliku wyjsciowego tez jest duza mimo ze czas petli for jest wtedy duzo mniejszy niz opoznienie jednostkowe samej funkcji _delay_ms(x)

    Dodano po 4 [godziny] 22 [minuty]:

    Załapałem już o co biega jak obejrzałem plik nagłówkowy avr\include\util\delay.h z deklaracja funkcji
    void_delay_ms(double __ms) oraz
    void_delay_us(double __us)
    które to wykorzystują funkcje _delay_loop_1/2() w zależności od argumentu funkcji i na dodatek operują na typie double. Gdy argument jest z góry określony kompilator wie która ścieżką pójdzie program a jak podstawie zmienna
    to musi uwzględnić wszelkie możliwe wartości czyli 0<x<255 lub 255<x<65535 lub x>65535 i wtedy obliczenia są na typie double który właśnie jest powodem tak dużego kodu wynikowego.

    Dzięki Wszystkim za odpowiedzi i naprowadzenie temat zamykam.
REKLAMA