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

[C][asm] Optymalizacja kodu przerwania

narasta 30 Paź 2010 18:42 2265 18
  • #1 8681852
    narasta
    Poziom 21  
    Kod mojego przerwania wygląda tak. Program działa w rezultacie tak jak bym chciał, ale samo wykonanie przerwania zajmuje trochę czasu. Zoptymalizowałem kod tak jak potrafiłem i w tym momencie liczę na Waszą pomoc.


    SW_pwm_12_channels:
    
    volatile unsigned char chanell[12];
    volatile const pwm_res = 32;
    volatile unsigned char counter = 0;
    
    #define PORTB_MASK  (1 << PB0)|(1 << PB1)|(1 << PB2)|(1 << PB3)|(1 << PB4)|(1 << PB5)|(1 << PB6)|(1 << PB7)
    #define PORTD_MASK  (1 << PD3)|(1 << PD4)|(1 << PD5)|(1 << PD6)
    #define DEBUG_PIN (1<<PD2)
    
    SIGNAL (SIG_OVERFLOW1)
    {
    	PORTD |= DEBUG_PIN;
    
    	TCNT1 = 64920; 	//f = 32000Hz / f_pwm = 1kHz / oryg = 64911
    	
    	if(counter == chanell[0]) PORTB &= ~(1<<0);
    	if(counter == chanell[1]) PORTB &= ~(1<<1);
    	if(counter == chanell[2]) PORTB &= ~(1<<2);
    	if(counter == chanell[3]) PORTB &= ~(1<<3);
    	if(counter == chanell[4]) PORTB &= ~(1<<4);
    	if(counter == chanell[5]) PORTB &= ~(1<<5);
    	if(counter == chanell[6]) PORTB &= ~(1<<6);
    	if(counter == chanell[7]) PORTB &= ~(1<<7);
    	
    	if(counter == chanell[8]) PORTD &= ~(1<<3);
    	if(counter == chanell[9]) PORTD &= ~(1<<4);
    	if(counter == chanell[10]) PORTD &= ~(1<<5);
    	if(counter == chanell[11]) PORTD &= ~(1<<6);
    	
    
    	if(counter == 31)
    	{
    		counter = 0;
    		PORTB |= PORTB_MASK;
    		PORTD |= PORTD_MASK;
    	}
    	counter++;
    
    	PORTD &= ~DEBUG_PIN;
    }
    
    void Init(void)
    {
     	DDRB |= PORTB_MASK;
    	DDRD |= PORTD_MASK;
    	DDRD |= DEBUG_PIN;
    
    	CLKPR = (1 << CLKPCE);        // enable clock prescaler update
    	CLKPR = 0;                    // set clock to maximum (= crystal)
    
    	TIFR = (1 << TOV1);           // clear interrupt flag
    	TIMSK = (1 << TOIE1);         // enable overflow interrupt
    	
    	TCCR1B &= ~(1<<CS12);
    	TCCR1B &= ~(1<<CS11);
    	TCCR1B |= (1<<CS10);
    
    	sei();
    }
    


    Zależy mi na optymalizacji samego przerwania. Kod może zajmować więcej (ramu też sporo jeszcze zostało), byleby udało się go trochę przyśpieszyć.

    Może nawet ktoś pokusiłby się o przepisanie tego do ASM i zrobienei z tego wstawki do C :)

    Z góry dzięki za wszelką pomoc.
  • Pomocny post
    #2 8681985
    hotdog
    Poziom 26  
    Witam. Na moje oko tego już bardzo nie zoptymalizujesz w C. Pokaż może do czego to Tobie kompilator tłumaczy na asm (plik z rozszerzeniem *.lss). Będziesz miał pojęcie jak takie coś wygląda w asm.
    Jedyne co widzę na C, to:
    PORTB |= PORTB_MASK;
    Masz wykorzystany cały port, więc powineneś napisać:
    PORTB = 0xFF;

    Zaoszczędzisz jedną operację odczytu z pamięci i jedną operację logicznego OR'a.

    Poza tym takie coś:
    if(counter == chanell[0]) PORTB &= ~(1<<0);

    Powineneś napisać tak:

    if(counter == chanell[0]) PORTB &= ~(1<<PB0);

    Ale to oczywiście tylko kosmetyka i nie przyśpieszy działania programu.

    Pozdrawiam
  • Pomocny post
    #3 8682014
    BoskiDialer
    Poziom 34  
    Jeśli zmienna counter jest typu volatile, jest to dla kompilatora informacja, że każde odwołanie musi się odwoływać do pamięci, co spowoduje wielokrotne przeładowanie zmiennej mimo, że nie ulega ona zmianie: można zrobić kopię lokalną na czas przerwania. Ciągłe odwołania do PORTB i PORTD jeśli nie zostaną zoptymalizowane do instrukcji cbi, to kod będzie względnie duży. Można trochę pokombinować i zmienić sposób generowania wartości na wyjścia: ciągłe testy w relacji mniejszości, co wbrew pozorom pozwala uprościć kod.

    Kod napisany na szybko. Możliwe, że logika na pinach będzie zanegowana, wtedy należy zmienić kolejność rejestrów przy wszystkich porównaniach.
    #include <avr/io.h>
    //include signals.h
    
    .extern counter
    .extern chanell
    
    .global SIG_OVERFLOW1
    SIG_OVERFLOW1:
    	push r25
    	in r25, _SFR_IO_ADDR(SREG)
    	push r25
    	push r24
    	push r22
    
    	// TCNT1 = 64920;
    	ldi r24, high(64920)
    	out _SFR_IO_ADDR(TCNT1H), r24
    	ldi r24, low(64920)
    	out _SFR_IO_ADDR(TCNT1L), r24
    
    	// cache zmiennej counter, ignorowanie 'volatile'
    	lds r24, counter
    
    	// utworzenie bajtu dla PORTB
    	lds r25, chanell+0
    	cp r25, r24
    	ror r22
    	lds r25, chanell+1
    	cp r25, r24
    	ror r22
    	lds r25, chanell+2
    	cp r25, r24
    	ror r22
    	lds r25, chanell+3
    	cp r25, r24
    	ror r22
    	lds r25, chanell+4
    	cp r25, r24
    	ror r22
    	lds r25, chanell+5
    	cp r25, r24
    	ror r22
    	lds r25, chanell+6
    	cp r25, r24
    	ror r22
    	lds r25, chanell+7
    	cp r25, r24
    	ror r22
    
    	// wystawienie bajtu na PORTB
    	out _SFR_IO_ADDR(PORTB), r22
    
    	// utworzenie bajtu dla PORTD
    	lds r25, chanell+8
    	cp r25, r24
    	ror r22
    	lds r25, chanell+9
    	cp r25, r24
    	ror r22
    	lds r25, chanell+10
    	cp r25, r24
    	ror r22
    	lds r25, chanell+11
    	cp r25, r24
    	ror r22
    
    	// zwiększenie counter, ograniczenie zakresu, write-back
    	inc r24
    	andi r24, 0x1F
    	sts counter, r24
    
    	// wystawienie bajtu na PORTD
    	lsr r22
    	andi r22, PORTD_MASK
    	in r24, _SFR_IO_ADDR(PORTD)
    	andi r24, ~PORTD_MASK
    	or r24, r22
    	out _SFR_IO_ADDR(PORTD), r24
    
    
    	pop r22
    	pop r24
    	pop r25
    	out _SFR_IO_ADDR(SREG), r25
    	pop r25
    	reti
    
  • Pomocny post
    #4 8682081
    tmf
    VIP Zasłużony dla elektroda
    Tak jak piszą koledzy - utworzyć lokalną kopię zmiennej counter, żeby nie była volatile w przerwaniu. Ale prawdziwa optymalizacja to zmiana algorytmu - jak pamiętam chodzi ci o 12-kanałowy PWM. Lepiej więc zrobić tablicę przechowującą stan kanałów w danej jednostce czasu, wtedy 16-kanałowy PWM to będzie tylko PORTB=channel[counter].lo; PORTD=channel[counter].hi. lo i hi to 8-bitowe pola.
  • #5 8682181
    BoskiDialer
    Poziom 34  
    Rozwiązanie z tablicą wartości w kolejnych przedziałach czasu jest dobre pod warunkiem, że jest dostępne dużo pamięci... na co jak teraz widzę autor przytaknął już na starcie. Oczywiście jeśli aktualizacja wartości kanałów nie występuje zbyt często, to jest to rozwiązanie bardzo dobre. Generowanie tablicy można zrobić dość szybko:
    - wyczyścić całą tablicę
    - dla każdego kanału wstawić jedynkę w odpowiednie miejsce
    - pętlą przejechać tablicę wstecz, używając sumy logicznej rozpropagować jedynkę na wcześniejsze komórki. Rozwiązanie podobne: tablica zawsze ustawiona (0xFF), w pierwszej wyłączonej komórce na odpowiednim bicie wpisać 0. Wtedy przerwanie może realizować kod PORTB &= ch_tab[..]; co jest małym nakładem do samego przypisania, jednak tablicę można łatwiej aktualizować: przywrócić jedynkę na starym miejscu, zrobić zero w nowym miejscu.

    Szybko wyskrobany kod. Założenie, że ch_tab wskazuje na tablicę 64 bajtów (gdy 32 kroki dla pwm).
    #include <avr/io.h>
    //include signals.h
    
    .extern counter
    .extern ch_tab
    
    .global SIG_OVERFLOW1
    SIG_OVERFLOW1:
    	push r25
    	in r25, _SFR_IO_ADDR(SREG)
    	push r25
    	push ZL
    	push ZH
    
    	// TCNT1 = 64920;
    	ldi r25, high(64920)
    	out _SFR_IO_ADDR(TCNT1H), r25
    	ldi r25, low(64920)
    	out _SFR_IO_ADDR(TCNT1L), r25
    
    	// buforowanie counter, aktualizacja
    	lds ZL, counter
    	inc ZL
    	andi ZL, 0x1F
    	sts counter, ZL
    	
    	// ustalenie wskaźnika na aktualny element
    	lsl ZL
    	ldi ZH, 0
    	rol ZH
    	subi ZL, low(-ch_tab)
    	sbci ZH, high(-ch_tab)
    	
    	// aktualizacja PORTB
    	ld r25, Z+
    	out _SFR_IO_ADDR(PORTB), r25
    
    	// aktualizacja PORTD
    	ld r25, Z
    	in ZL, _SFR_IO_ADDR(PORTD)
    	andi ZL, ~PORTD_MASK
    	or ZL, r25
    	out _SFR_IO_ADDR(PORTD), ZL
    
    	pop ZH
    	pop ZL
    	pop r25
    	out _SFR_IO_ADDR(SREG), r25
    	pop r25
    	reti
  • #6 8682308
    narasta
    Poziom 21  
    Ok BoskiDialer, a jak Twój kod wstawić do C? da się tak, żeby nie pisać w każdej linijce asm("");?
  • Pomocny post
    #7 8682397
    BoskiDialer
    Poziom 34  
    Lepiej dla Ciebie będzie wrzucić ten kod do pliku .s i dodać go do kompilacji. Użycie wstawek będzie nieefektywne, kompilator utworzy swój prolog i epilog dla przerwania (o ile nie użyjesz atrybutu 'naked'), a poza tym nie ma sensu trzymać takiej długiej wstawki jak można dodać jeden plik w całości w asm.
  • #8 8682439
    narasta
    Poziom 21  
    Hm... ok zrobiłem osobny source file o rozszerzeniu *.s ale dostaje troche błędów...

    
    rm -rf main.o  ledy.elf dep/* ledy.hex ledy.eep ledy.lss ledy.map
    Build succeeded with 0 Warnings...
    avr-gcc  -mmcu=attiny2313 -Wall -gdwarf-2 -std=gnu99         -DF_CPU=20000000UL -Os -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums -MD -MP -MT main.o -MF dep/main.o.d  -c  ../main.c
    In file included from ../main.c:3:
    c:/winavr-20100110/lib/gcc/../../avr/include/avr/signal.h:36:2: warning: #warning "This header file is obsolete.  Use <avr/interrupt.h>."
    avr-gcc  -mmcu=attiny2313 -mmcu=attiny2313 -Wall -gdwarf-2 -std=gnu99         -DF_CPU=20000000UL -Os -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums -MD -MP -MT wstawka.o -MF dep/wstawka.o.d  -x assembler-with-cpp -Wa,-gdwarf2 -c  ../wstaw
    ka.s
    
    ../wstawka.s: Assembler messages:
    ../wstawka.s:16: Error: garbage at end of line
    ../wstawka.s:18: Error: garbage at end of line
    ../wstawka.s:31: Error: garbage at end of line
    ../wstawka.s:32: Error: garbage at end of line
    make: *** [wstawka.o] Error 1
    Build failed with 1 errors and 1 warnings...
    
  • Pomocny post
    #9 8682787
    Andrzej__S
    Poziom 28  
    Zamiast high() i low() spróbuj użyć hi8() i lo8().
  • #10 8683006
    narasta
    Poziom 21  
    Ok, dzięki hi8 i lo8 nie wyskakują tamte błędy, ale za to teraz otrzymuję to:

    
    Build started 30.10.2010 at 23:00:46
    avr-gcc  -mmcu=attiny2313 -mmcu=attiny2313 -Wall -gdwarf-2 -std=gnu99             -DF_CPU=20000000UL -Os -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums -MD -MP -MT wstawka.o -MF dep/wstawka.o.d  -x assembler-with-cpp -Wa,-gdwarf2 -c  ../w
    stawka.s
    ../wstawka.s: Assembler messages:
    ../wstawka.s:34: Error: can't resolve `0' {*UND* section} - `ch_tab' {*UND* section}
    ../wstawka.s:34: Error: expression too complex
    ../wstawka.s:35: Error: can't resolve `0' {*UND* section} - `ch_tab' {*UND* section}
    ../wstawka.s:35: Error: expression too complex
    make: *** [wstawka.o] Error 1
    Build failed with 1 errors and 0 warnings...
    
  • #12 8683515
    BoskiDialer
    Poziom 34  
    Dawno nie pisałem bezpośrednio w asm, jednak minus musi być. Jak patrzę po innych moich źródłach, powinno być lo8(-(ch_tab)) oraz hi8(-(ch_tab)). Chodzi tutaj o pewną sztuczkę, jako że avr'y nie mają dodawania stałej należy odjąć stałą ze zmienionym znakiem.
  • #13 8683711
    mirekk36
    Poziom 42  
    BoskiDialer --> ale jest dodawanie stałej tyle że 16bit do słowa ADIW - tak z ciekawości tylko pytam (bo nie sprawdzam teraz na żywym kodzie) - nie dałoby rady tego tak zrobić?

    
       // ustalenie wskaźnika na aktualny element 
       lsl ZL 
       ldi ZH, 0 
       rol ZH 
       adiw ZX, ch_tab 
    
  • Pomocny post
    #14 8683844
    BoskiDialer
    Poziom 34  
    Instrukcja adiw ma duże ograniczenia: stała musi być w zakresie od 0 do 63, a rejestry muszą być jedną z czterech dozwolonych dla tej instrukcji par. Do małych stałych instrukcja adiw jest bardzo dobra, jednak jeśli trzeba dodać większą stałą lub wartość, która jest znana dopiero na poziomie linkera, stosuje się subi/sbci z ujemną stałą. Dodatkowo instrukcja adiw wykonuje się w 2 cyklach, a subi/sbci wymagają po jednym cyklu, tak więc jedyna oszczędność wychodzi tylko na rozmiarze kodu wynikowego.
  • #16 8684888
    narasta
    Poziom 21  
    A no tak zapomniałem o przycisku pomógł :P

    z takim kodem
    
    #include <avr/io.h>
    
    //include signals.h
    
    #define PORTD_MASK  (1 << PD3)|(1 << PD4)|(1 << PD5)|(1 << PD6)
    
    .extern counter
    .extern ch_tab
    
    .global SIG_OVERFLOW1
    SIG_OVERFLOW1:
       push r25
       in r25, _SFR_IO_ADDR(SREG)
       push r25
       push ZL
       push ZH
    
       // TCNT1 = 64920;
       ldi r25, hi8(64920)
       out _SFR_IO_ADDR(TCNT1H), r25
       ldi r25, lo8(64920)
       out _SFR_IO_ADDR(TCNT1L), r25
    
       // buforowanie counter, aktualizacja
       lds ZL, counter
       inc ZL
       andi ZL, 0x1F
       sts counter, ZL
       
       // ustalenie wskaźnika na aktualny element
       lsl ZL
       ldi ZH, 0
       rol ZH
       subi ZL, lo8(-(ch_tab))
       sbci ZH, hi8(-(ch_tab))
       
       // aktualizacja PORTB
       ld r25, Z+
       out _SFR_IO_ADDR(PORTB), r25
    
       // aktualizacja PORTD
       ld r25, Z
       in ZL, _SFR_IO_ADDR(PORTD)
       andi ZL, ~PORTD_MASK
       or ZL, r25
       out _SFR_IO_ADDR(PORTD), ZL
    
       pop ZH
       pop ZL
       pop r25
       out _SFR_IO_ADDR(SREG), r25
       pop r25
       reti
    


    nadal wywala mi błedy:

    
    Build started 31.10.2010 at 14:05:29
    avr-gcc -mmcu=attiny2313 -Wl,-Map=ledy.map main.o wstawka.o     -o ledy.elf
    wstawka.o: In function `__vector_5':
    C:\Users\...\[C] - tiny2313\default/../wstawka.s:12: multiple definition of `__vector_5'
    main.o:C:\Users\...\[C] - tiny2313\default/../main.c:8: first defined here
    wstawka.o: In function `__vector_5':
    (.text+0x24): undefined reference to `ch_tab'
    wstawka.o: In function `__vector_5':
    (.text+0x26): undefined reference to `ch_tab'
    make: *** [ledy.elf] Error 1
    Build failed with 2 errors and 0 warnings...
    
    
  • #17 8684937
    BoskiDialer
    Poziom 34  
    Cytat:
    C:\Users\adam\Desktop\Elektronika\CDC\v2 na FTDI\[C] - tiny2313\default/../wstawka.s:12: multiple definition of `__vector_5'
    main.o:C:\Users\adam\Desktop\Elektronika\CDC\v2 na FTDI\[C] - tiny2313\default/../main.c:8: first defined here

    Wektor przerwania możesz mieć tylko w jednym z plików: albo w main.c albo we wstawka.s

    Cytat:
    wstawka.o: In function `__vector_5':
    (.text+0x24): undefined reference to `ch_tab'
    wstawka.o: In function `__vector_5':
    (.text+0x26): undefined reference to `ch_tab'

    Warto by w main.c utworzyć zmienną globalną ch_tab która miała by 64 bajty lub 32 komórki każda typu uint16_t. Będzie ona musiała być generowana tak jak to było omawiane: każda komórka (tutaj 2 bajty) zawiera wartości wyjść w danym przedziale czasu, tak więc należy wyzerować tablicę, wpisać jedynki i rozpropagować wstecz, ale to już można zrobić w C. Dolny bajt będzie dla PORTB, górny bajt (bez wyrównania, odpowiednie bity idą na odpowiednie piny) dla PORTD
  • #18 8684953
    narasta
    Poziom 21  
    narazie wywala mi jeszcze
    C:\Users\...\[C] - tiny2313\default/../wstawka.s:34: undefined reference to `ch_tab'
    C:\Users\...\[C] - tiny2313\default/../wstawka.s:35: undefined reference to `ch_tab'
    
  • #19 8685002
    BoskiDialer
    Poziom 34  
    To oznacza, że nie utworzyłeś zmiennej globalnej o podanej nazwie lub nie jest ona widoczna. Wskazówka jak wcześniej, ewentualnie możesz pokazać co masz w main.c
REKLAMA