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

[ATMEGA168][AVRGCC] Przerwania optymalizacja szybkości

Piotr Piechota 28 Wrz 2008 14:02 1776 10
  • #1 5579087
    Piotr Piechota
    Poziom 22  
    Witam

    Przy pomocy Timera0 procesora ATmega168 tworzę falę PWM do sterowania silnikiem bezszczotkowym. Przerwanie od przepełnienia licznika załącza odpowiednie (w zależności od stanu czujników halla wirnika silnika) tranzystory mostka 3-fazowego a przerwanie od porównania wyłącza. Zależy mi na jak najkrótszym czasie wykonywania procedur przerwań.
    W zmiennych
    volatile uint8_t pwm_on, pwm_off
    mam już przygotowane wartości do wysłania do portu i procedury przerwań są dość banalne:
    
    ISR(TIMER0_OVF_vect)  // przekroczenie timera0 - załaczenie w PWM
    {
     PORTD = pwm_on;
    }
    
    ISR(TIMER0_COMPA_vect)  //porownanie timera0 - wylaczanie w PWM
    {
     PORTD = pwm_off;
    }


    Jak podejrzałem w AVRStudio procedury są rozwijane do:
    
    @0000015B: __vector_16
    169:      {
    +0000015B:   921F        PUSH    R1               Push register on stack
    +0000015C:   920F        PUSH    R0               Push register on stack
    +0000015D:   B60F        IN      R0,0x3F          In from I/O location
    +0000015E:   920F        PUSH    R0               Push register on stack
    +0000015F:   2411        CLR     R1               Clear Register
    +00000160:   938F        PUSH    R24              Push register on stack
    170:       PORTD = pwm_on;
    +00000161:   91800106    LDS     R24,0x0106       Load direct from data space - w 0x106 jest pwm_on
    +00000163:   B98B        OUT     0x0B,R24         Out to I/O location
    171:      }
    +00000164:   918F        POP     R24              Pop register from stack
    +00000165:   900F        POP     R0               Pop register from stack
    +00000166:   BE0F        OUT     0x3F,R0          Out to I/O location
    +00000167:   900F        POP     R0               Pop register from stack
    +00000168:   901F        POP     R1               Pop register from stack
    +00000169:   9518        RETI                     Interrupt return
    @0000016A: __vector_14
    174:      {
    +0000016A:   921F        PUSH    R1               Push register on stack
    +0000016B:   920F        PUSH    R0               Push register on stack
    +0000016C:   B60F        IN      R0,0x3F          In from I/O location
    +0000016D:   920F        PUSH    R0               Push register on stack
    +0000016E:   2411        CLR     R1               Clear Register
    +0000016F:   938F        PUSH    R24              Push register on stack
    175:       PORTD = pwm_off;
    +00000170:   9180011E    LDS     R24,0x011E       Load direct from data space - w 0x11e jest pwm_off
    +00000172:   B98B        OUT     0x0B,R24         Out to I/O location
    176:      }
    +00000173:   918F        POP     R24              Pop register from stack
    +00000174:   900F        POP     R0               Pop register from stack
    +00000175:   BE0F        OUT     0x3F,R0          Out to I/O location
    +00000176:   900F        POP     R0               Pop register from stack
    +00000177:   901F        POP     R1               Pop register from stack
    +00000178:   9518        RETI                     Interrupt return
    
    


    Czy to jest optymalny kod? Można jakoś zmusić kompilator żeby zrobił to lepiej czy może da się to napisać samemu w assemblerze.

    Pozdrawiam
    Piotr
  • #2 5579667
    zumek
    Poziom 39  
    Piotr Piechota napisał:

    Czy to jest optymalny kod? Można jakoś zmusić kompilator żeby zrobił to lepiej czy może da się to napisać samemu w assemblerze.

    Pozdrawiam
    Piotr

    Kompilatora , raczej nie przekonasz ;)
    Jeśli te procedury mają być szybkie , to z tego co wygenerował kompilator , można zostawić tyle:
    
    
    ;informacje dla kompilatora
       .section .text
       itd.
    
    ;kod
    TIMER0_OVF_vect:
    	PUSH R24
    	LDS  R24,pwm_on
    	OUT  PORTD,R24	 ;0x18,R24
    	POP  R24
    	RETI	
    TIMER0_COMPA_vect:
    	PUSH R24
    	LDS  R24,pwm_off
    	OUT  PORTD,R24   ;0x18,R24
    	POP  R24
    	RETI	
    

    Umieścić wszystko w osobnym pliku *.S i dołączyć ten plik do projektu.

    Piotrek
  • #3 5579671
    ZbeeGin
    Poziom 39  
    Piotr Piechota napisał:
    Czy to jest optymalny kod? Można jakoś zmusić kompilator żeby zrobił to lepiej czy może da się to napisać samemu w assemblerze.

    Kod rzeczywiście nie jest optymalny. Obsługa rejestrów R0 i R1 jest nie potrzebna. SREG mógłby lądować co R24 który jako jedyny byłby tutaj modyfikowany i zabezpieczany na stos.
    Lepiej jednak będzie te fragmenty napisać w asm, i polecić kompialtorowi by nie dodawał prologu i epilogu przerwania za pomocą atrybutu ISR_NAKED. Pamiętaj tylko aby SREG został zabezpieczony i na końcu znalazło się RETI (kompilator wtedy go nie dodaje).
  • Pomocny post
    #4 5579681
    zumek
    Poziom 39  
    ZbeeGin napisał:
    Pamiętaj tylko aby SREG został zabezpieczony i na końcu znalazło się RETI (kompilator wtedy go nie dodaje).

    Żadna z w/w instrukcji , nie powoduje zmian w SREG(nie modyfikuje flag) , więc nie ma potrzeby , odkładać go na stos.

    Piotrek

    PS
    Mam na myśli "przycięty" kod , który pokazałem , bo w tym wygenerowanym przez GCC , instrukcja CLR modyfikuje SREG.
  • #5 5579920
    Piotr Piechota
    Poziom 22  
    Dzięki za podpowiedzi. Nigdy nie dołączałem modułów w assemblerze do moich projekcików i nie wiem czy dobrze zrobiłem. Nie mam w tej chwili platformy sprzętowej ale sprawdzałem zaproponowane rozwiązanie w symulatorze AVRStudio. Dodany plik przerwania.s:
    #include <avr/io.h>   
    
    .section .text
    
    .global TIMER0_OVF_vect
    .global TIMER0_COMPA_vect
    
    ;kod
    
    TIMER0_OVF_vect:
       PUSH R24
       LDS  R24,pwm_on
       OUT  PORTD,R24    ;0x18,R24
       POP  R24
       RETI   
    
    TIMER0_COMPA_vect:
       PUSH R24
       LDS  R24,pwm_off
       OUT  PORTD,R24   ;0x18,R24
       POP  R24
       RETI 


    Niestety nie potrafię śledzić tego co jest w procedurze przerwania ale patrząc na licznik cykli w symulatorze wychodzi mi że obsługa przerwania z odchudzoną procedurą to 1.6 us a był 2.2us (20MHz) tak że niezły wynik :) .
    Jak trzeba zaczarowć środowisko AVRStudio żeby śledzić źródła w assemblerze?
    W pliku *.lss znalazłem coś takiego:

    000006cc <__vector_16>:
     6cc:	8f 93       	push	r24
     6ce:	80 91 06 01 	lds	r24, 0x0106
     6d2:	8b bd       	out	0x2b, r24	; 43
     6d4:	8f 91       	pop	r24
     6d6:	18 95       	reti
    
    000006d8 <__vector_14>:
     6d8:	8f 93       	push	r24
     6da:	80 91 1e 01 	lds	r24, 0x011E
     6de:	8b bd       	out	0x2b, r24	; 43
     6e0:	8f 91       	pop	r24
     6e2:	18 95       	reti
    

    tak że chyba procedura powinna działać bo wcześniej gdy brakowało lini:
    .global TIMER0_OVF_vect
    .global TIMER0_COMPA_vect
    

    procesor resetował się grzecznie jak chciałem zgłościś przerwanie.
    Pozdrawiam
    Piotr
  • #7 5580037
    Piotr Piechota
    Poziom 22  
    Super :)
    Teraz jak śledziłem w oknie disassebmlera wyszło że kompletna obsługa przerwania to 0.9 us :D - o to chodziło (było 2.2us)
    Poprzednio jak nie widziałem co się wykonuje okazało się że wykonują mi się obie procedury przerwania do przepełnienia i porównania jedna po drugiej stąd fałszywy wynik - mój błąd

    Pozdrawiam
  • #8 5580047
    zumek
    Poziom 39  
    Piotr Piechota napisał:
    ... Nigdy nie dołączałem modułów w assemblerze do moich projekcików i nie wiem czy dobrze zrobiłem.

    No niestety , nie popisałeś się :-P
    Zobacz na adres , do którego jest kopiowany R24
    
     6d2:	8b bd       	out	0x2b, r24	; 43
    

    r24 został skopiowany do GPIOR2(0x2B) , a nie do PORTD(0x0B) i zonk
    :|
    Nie chce mi sie tłumaczyć dlaczego tak się stało , więc dam Ci gotowca , a Ty doczytaj dlaczego tak , a nie inaczej :D
    
    #define __SFR_OFFSET 0
    #include <avr/io.h>
    	
    	.section .text
        .global TIMER0_OVF_vect
        .global TIMER0_COMPA_vect
        .type   TIMER0_OVF_vect, @function
        .type   TIMER0_COMPA_vect, @function
    	;.extern pwm_on,pwm_off
    
    TIMER0_OVF_vect:
    	PUSH R24
    	LDS  R24,pwm_on
    	OUT  PORTD,R24	 ;0x18,R24
    	POP  R24
    	RETI	
    TIMER0_COMPA_vect:
    	PUSH R24
    	LDS  R24,pwm_off
    	OUT  PORTD,R24   ;0x18,R24
    	POP  R24
    	RETI
    


    Źródeł w plikach *.S , nie da się śledzić , ale można użyć Menu->View->Disassembler i tam założyć sobie pułapki.

    Piotrek
  • #9 5580114
    Piotr Piechota
    Poziom 22  
    Dzięki za gotowca teraz mam tak:
    +00000366:   938F        PUSH    R24              Push register on stack
    +00000367:   91800106    LDS     R24,0x0106       Load direct from data space
    +00000369:   B98B        OUT     0x0B,R24         Out to I/O location
    +0000036A:   918F        POP     R24              Pop register from stack
    +0000036B:   9518        RETI                     Interrupt return
    +0000036C:   938F        PUSH    R24              Push register on stack
    +0000036D:   9180011E    LDS     R24,0x011E       Load direct from data space
    +0000036F:   B98B        OUT     0x0B,R24         Out to I/O location
    +00000370:   918F        POP     R24              Pop register from stack
    +00000371:   9518        RETI                     Interrupt return

    Czytałem na temat automatycznego dodawanie 0x20. Trochę to zagmatwane i niestety wychodzi na to że bez gruntownej znajomości architektury kontrolera wiele się nie zdziała :(
    Edit:
    i znajomości 'ułatwień' zaszytych w kompilatorach
    Edit2:
    Czy średnik w
    ;.extern pwm_on,pwm_off
    

    był zamierzony?
  • #10 5580286
    BoskiDialer
    Poziom 34  
    Co do automatycznego dodawania "0x20" - chodzi tutaj o to, że rejestry [większość] są dostępne poprzez instrukcje in/out (przestrzeń io), ale jednocześnie są dostępne przez przestrzeń pamięci (wewnątrz niej przestrzeń io zaczyna się od adresu 0x20). Najlepszym rozwiązaniem przy kodzie pisanym w asemblerze jest zamiast stosować surową wartość np PORTD, stosować _SFR_MEM_ADDR(PORTD) [przy instrukcjach odwołujących się do przestrzeni pamięci] lub _SFR_IO_ADDR(PORTD) [odwołując się poprzez in/out]. Pisząc w C wszystkie rejestry są opisane adresem w przestrzeni pamięci, ale jeśli kompilator potrafi obliczyć adres i jest on w zakresie przestrzeni io, automatycznie zmniejsza adres o 0x20 i zmienia odwołania na in/out. Osobiście stosuję tylko _SFR_IO_ADDR(PORTD) [in/out] lub samo PORTD[lds/sts etc]. Warto pamiętać, jeśli kod się nie kompiluje przy instrukcji in/out, może rejestr należy do rozszerzonej przestrzeni i trzeba odwoływać się za pomocą instrukcji lds/sts.
  • #11 5580429
    Piotr Piechota
    Poziom 22  
    Dzięki Wszystkim za pomoc.

    Jako gratis filmik z testów robota w którym optymalizujecie procedury przerwań. Nie wiem tylko czy mi tego administracja tego forum nie wywali ;) bo to troszkę poza głównym wątkiem.

    [ATMEGA168][AVRGCC] Przerwania optymalizacja szybkości

    Pozdrawiam Piotr
REKLAMA